Search by

    Terakhir diperbaharui: Jan 5, 2020

    Update User Interface (Tailwind CSS) #2

    Pada bagian ini kita akan tambahkan Tailwind CSS ke aplikasi DinoTes.

    Persiapan

    Clone repository dari aplikasi DinoTes disini.

    Install dependency dengan jalankan perintah yarn install atau npm install.

    Step by step

    Karena saat ini aplikasi DinoTes menggunakan styled-components, kita perlu tambahkan package twin.macro untuk bisa menggunakan tailwindcss di dalam styled-components.

    Tanpa menggunakan twin.macro maka kita harus mengkonfigurasi keduanya secara manual dengan langkah konfigurasi yang cukup panjang.

    Install tailwindcss + twin.macro

    1yarn add tailwindcss twin.macro

    Tambahkan global styles yang berfungsi sama dengan preflight yang dimiliki tailwindcss.

    src/App.js

    1import React from 'react';
    2import { Route, Switch } from 'react-router-dom';
    3import styled from 'styled-components';
    4import { GlobalStyles } from 'twin.macro';
    5import HomePage from './pages/Home';
    6import AddPage from './pages/Add';
    7import EditPage from './pages/Edit';
    8
    9const Container = styled.div`
    10 text-align: center;
    11`;
    12
    13function App() {
    14 return (
    15 <div>
    16 <GlobalStyles />
    17 <Container>
    18 <Switch>
    19 <Route path="/add">
    20 <AddPage />
    21 </Route>
    22 <Route path="/edit/:id">
    23 <EditPage />
    24 </Route>
    25 <Route path="/">
    26 <HomePage />
    27 </Route>
    28 </Switch>
    29 </Container>
    30 </div>
    31 );
    32}
    33
    34export default App;

    Buat konfigurasi untuk twin.macro.

    Update package.json

    1...
    2 'babelMacros': {
    3 'twin': {
    4 'preset': 'styled-components'
    5 }
    6 }
    7..

    Jalankan aplikasi dengan perintah yarn start. Hasilnya:

    after install twin macro

    Jika diamati, hasilnya menjadi sedikit berantakan.

    Kita perlu tulis ulang semua css yang ada di dalam styled component menggunakan utility class dari tailwindcss, sekaligus melakukan sedikit perubahan pada tampilan atau UI aplikasi dinotes menjadi seperti ini:

    Gambar

    Page Layout

    Yang pertama kita update adalah layout.

    Kita tambahkan component PageContainer sebagai container untuk halaman.

    src/layouts/PageLayout.js

    1import React from 'react';
    2import tw from 'twin.macro';
    3import Header from '../components/shared/Header';
    4import Footer from '../components/shared/Footer';
    5
    6const PageContainer = tw.div`max-w-7xl mx-auto px-4 sm:px-6`;
    7
    8const PageLayout = (props) => {
    9 const { children } = props;
    10
    11 return (
    12 <PageContainer>
    13 <Header />
    14 {children}
    15 <Footer />
    16 </PageContainer>
    17 );
    18};
    19
    20export default PageLayout;

    Utility class yang ditambahkan pada component PageContainer adalah:

    • max-w-7xl, max-width dari container adalah 7xl atau 80rem
    • mx-auto, margin top & bottom auto
    • px-4, ukuran padding adalah 1rem
    • sm:px-6, pada ukuran layar 640px keatas ukuran padding top dan bottom berubah menjadi 1.5rem

    Shared Component

    Selanjutnya adalah component Header & Footer.

    Kita akan pindahkan button untuk menambah note baru ke Header, dan juga kita akan tambahkan FontAwesome Icon pada button agar membuatnya menjadi lebih menarik.

    src/components/shared/Header.js

    1import React from 'react';
    2import tw from 'twin.macro';
    3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    4import { faFile } from '@fortawesome/free-solid-svg-icons';
    5
    6import Button from '../ui/Button';
    7import logo from '../../assets/images/header-logo.png';
    8import { Link } from 'react-router-dom';
    9
    10const Navigation = tw.div`
    11 flex
    12 justify-between items-center
    13 border-b-2 border-gray-100
    14 py-6
    15 md:justify-start md:space-x-3
    16 `;
    17
    18const Img = tw.img`h-14 w-auto sm:h-16`;
    19
    20const Heading = tw.h2`
    21 invisible
    22 text-xl font-bold text-gray-900
    23 md:visible`;
    24
    25const Menu = tw.div`md:flex items-center justify-end md:flex-1 lg:w-0`;
    26
    27const Header = () => {
    28 return (
    29 <Navigation>
    30 <Img src={logo} alt="logo" />
    31 <Heading>DinoTes</Heading>
    32 <Menu>
    33 <Link to="/add">
    34 <Button>
    35 <FontAwesomeIcon icon={faFile} />
    36 &nbsp;&nbsp; New Note
    37 </Button>
    38 </Link>
    39 </Menu>
    40 </Navigation>
    41 );
    42};
    43
    44export default Header;

    Kemudian update footer.js

    src/components/shared/Footer.js

    1import React from 'react';
    2import tw from 'twin.macro';
    3
    4const Container = tw.div`m-4 p-2`;
    5
    6const Footer = () => {
    7 return (
    8 <Container>
    9 <p>
    10 by <a href="https://devsaurus.com">devsaurus</a> &copy; 2020
    11 </p>
    12 </Container>
    13 );
    14};
    15
    16export default Footer;

    UI Component

    UI component disini meliputi Button, Container, Form dan Message.

    src/components/ui/Button.js

    Update code Button.js dari sebelumnya:

    1import styled from 'styled-components';
    2
    3const Button = styled.button`
    4 background: ${(props) => (props.danger ? '#F56565' : '#3182ce')};
    5 color: white;
    6 font-size: 1em;
    7 margin: 1rem 0;
    8 padding: 0.75rem;
    9 border: 2px solid white;
    10 border-radius: 5px;
    11`;
    12
    13export default Button;

    menjadi:

    1import tw, { styled } from 'twin.macro';
    2
    3const Button = styled.button(({ danger }) => [
    4 danger ? tw`bg-red-500` : tw`bg-purple-700`,
    5 tw`text-white text-base m-2 p-3 border rounded-md`
    6]);
    7
    8export default Button;

    Code menjadi sangat simple!

    Lanjutkan dengan ui component yang lain:

    src/components/ui/Container.js

    1import tw from 'twin.macro';
    2
    3const Container = tw.div`flex flex-col items-center justify-center m-4`;
    4
    5export default Container;

    src/components/ui/Form.js

    1import tw from 'twin.macro';
    2
    3const Form = tw.form`flex flex-col w-4/5`;
    4
    5const FormGroup = tw.div`flex flex-col`;
    6
    7const FormButtonGroup = tw.div`flex justify-end`;
    8
    9const Input = tw.input`
    10 my-8 p-2
    11 text-xl font-bold w-full
    12 focus:outline-none focus:ring focus:border-blue-300`;
    13
    14const TextArea = tw.textarea`
    15 resize-y
    16 mb-8 p-2
    17 focus:outline-none focus:ring focus:border-blue-300`;
    18
    19export { Form, FormGroup, FormButtonGroup, Input, TextArea };

    src/components/ui/Message.js

    1import React from 'react';
    2import tw, { styled } from 'twin.macro';
    3
    4const MessageContainer = styled.div(({ danger }) => [
    5 danger ? tw`border-red-500` : tw`border-green-500`,
    6 tw`flex flex-col items-center justify-center m-4 p-4 border-2 rounded`
    7]);
    8
    9const Message = (props) => {
    10 const { text, type } = props;
    11
    12 return (
    13 <>
    14 {type === 'error' ? (
    15 <MessageContainer danger>
    16 <p>{text}</p>
    17 </MessageContainer>
    18 ) : (
    19 <MessageContainer>
    20 <p>{text}</p>
    21 </MessageContainer>
    22 )}
    23 </>
    24 );
    25};
    26
    27export default Message;

    Component lainnya

    src/components/AddNoteForm.js

    1...
    2import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    3import { faSave } from '@fortawesome/free-solid-svg-icons';
    4...
    5 return (
    6 <>
    7 <InfoWrapper status={isSuccess} />
    8 <Form onSubmit={handleSubmit}>
    9 <FormGroup>
    10 <Input
    11 type='text'
    12 name='title'
    13 placeholder='Title'
    14 value={title}
    15 onChange={handleTitleChange}
    16 />
    17 </FormGroup>
    18 <FormGroup>
    19 <TextArea
    20 name='note'
    21 rows='12'
    22 placeholder='Your content goes here..'
    23 value={note}
    24 onChange={handleNoteChange}
    25 />
    26 </FormGroup>
    27 <FormButtonGroup>
    28 <Button type='submit'>
    29 <FontAwesomeIcon icon={faSave} /> &nbsp; Save
    30 </Button>
    31 </FormButtonGroup>
    32 </Form>
    33 </>
    34 );
    35...

    Hasilnya:

    dinotes new note

    src/components/EditNoteForm.js

    1...
    2import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    3import { faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
    4import { Form, FormGroup, FormButtonGroup, Input, TextArea } from './ui/Form';
    5...
    6 return (
    7 <>
    8 <InfoWrapper status={isSuccess} />
    9 <Form onSubmit={handleSubmit}>
    10 <FormGroup>
    11 <Input
    12 type='text'
    13 name='title'
    14 value={title}
    15 onChange={handleTitleChange}
    16 />
    17 </FormGroup>
    18 <FormGroup>
    19 <TextArea
    20 name='note'
    21 rows='12'
    22 value={note}
    23 onChange={handleNoteChange}
    24 />
    25 </FormGroup>
    26 <FormButtonGroup>
    27 <Button type='submit'>
    28 <FontAwesomeIcon icon={faSave} /> &nbsp; Save
    29 </Button>
    30 <Button danger onClick={handleDeleteNote}>
    31 <FontAwesomeIcon icon={faTrashAlt} /> &nbsp; Delete
    32 </Button>
    33 </FormButtonGroup>
    34 </Form>
    35 </>
    36 );
    37...

    Hasilnya:

    dinotes edit note

    Untuk component yang bertugas menampilkan semua notes, kita akan ganti tampilan dari list ke grid.

    src/components/NotesList.js

    1import React, { useEffect } from 'react';
    2import tw from 'twin.macro';
    3import { Link } from 'react-router-dom';
    4import { useSelector, useDispatch } from 'react-redux';
    5
    6import { getAllNotes, fetchNotes } from '../features/notes/notesSlice';
    7
    8const NotesListContainer = tw.div`grid grid-cols-1 md:grid-cols-3 gap-4 my-8`;
    9const Card = tw.div`text-left p-4 border rounded-md`;
    10const Title = tw.h4`text-lg font-semibold text-purple-900`;
    11
    12const NotesList = () => {
    13 const dispatch = useDispatch();
    14 const notes = useSelector(getAllNotes);
    15 const notesStatus = useSelector((state) => state.notes.status);
    16 const error = useSelector((state) => state.notes.error);
    17
    18 useEffect(() => {
    19 if (notesStatus === 'idle') {
    20 dispatch(fetchNotes());
    21 }
    22 }, [notesStatus, dispatch]);
    23
    24 let content;
    25
    26 if (notesStatus === 'loading') {
    27 content = <div>Loading...</div>;
    28 } else if (notesStatus === 'succeeded') {
    29 content = notes.map((note) => {
    30 return (
    31 <Card key={note._id}>
    32 <Title>
    33 <Link to={`/edit/${note._id}`}>{note.title}</Link>
    34 </Title>
    35 <p>{note.note.slice(0, 101)}</p>
    36 </Card>
    37 );
    38 });
    39 } else if (notesStatus === 'failed') {
    40 content = <div>{error}</div>;
    41 }
    42
    43 return <NotesListContainer>{content}</NotesListContainer>;
    44};
    45
    46export default NotesList;

    dinotes final ui

    Pages

    Ada 3 halaman atau page yang harus diupdate, yaitu halaman untuk menambah note, halaman untuk mengedit note dan halaman untuk menampilkan semua note.

    Karena pada setiap page terdapat sebuah link sebagai navigasi kembali halaman Home, kita akan buat ui component baru dengan nama HomeLink.js

    src/ui/HomeLink.js

    1import tw from 'twin.macro';
    2
    3const HomeLink = tw.div`flex w-full`;
    4
    5const Title = tw.h4`text-lg font-semibold text-blue-500`;
    6
    7export { HomeLink, Title };

    Update halaman untuk menambah note baru:

    src/pages/Add.js

    1import React from 'react';
    2import { Link } from 'react-router-dom';
    3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    4import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
    5import PageLayout from '../layouts/PageLayout';
    6import AddNoteForm from '../components/AddNoteForm';
    7import Container from '../components/ui/Container';
    8import { HomeLink, Title } from '../components/ui/HomeLink';
    9
    10const AddPage = () => {
    11 return (
    12 <PageLayout>
    13 <Container>
    14 <HomeLink>
    15 <Title>
    16 <FontAwesomeIcon icon={faArrowLeft} /> &nbsp; <Link to="/">Back</Link>
    17 </Title>
    18 </HomeLink>
    19 <AddNoteForm />
    20 </Container>
    21 </PageLayout>
    22 );
    23};
    24
    25export default AddPage;

    Update halaman untuk mengedit note:

    src/pages/Edit.js

    1import React from 'react';
    2import { Link } from 'react-router-dom';
    3import PageLayout from '../layouts/PageLayout';
    4import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    5import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
    6import EditNoteForm from '../components/EditNoteForm';
    7import Container from '../components/ui/Container';
    8import { HomeLink, Title } from '../components/ui/HomeLink';
    9
    10const EditPage = () => {
    11 return (
    12 <PageLayout>
    13 <Container>
    14 <HomeLink>
    15 <Title>
    16 <FontAwesomeIcon icon={faArrowLeft} /> &nbsp; <Link to="/">Back</Link>
    17 </Title>
    18 </HomeLink>
    19 <EditNoteForm />
    20 </Container>
    21 </PageLayout>
    22 );
    23};
    24
    25export default EditPage;

    Update halaman utama dari aplikasi DinoTes.

    Karena button untuk menambah note baru dipindah ke header maka kita bisa hapus button Add New Note.

    1import React from 'react';
    2import PageLayout from '../layouts/PageLayout';
    3import NotesList from '../components/NotesList';
    4import Container from '../components/ui/Container';
    5
    6const HomePage = () => {
    7 return (
    8 <PageLayout>
    9 <Container>
    10 <NotesList>Notes List</NotesList>
    11 </Container>
    12 </PageLayout>
    13 );
    14};
    15
    16export default HomePage;

    Update App.js

    Langkah terakhir adalah update App.js

    src/App.js

    1import React from 'react';
    2import { Route, Switch } from 'react-router-dom';
    3import tw, { GlobalStyles } from 'twin.macro';
    4import HomePage from './pages/Home';
    5import AddPage from './pages/Add';
    6import EditPage from './pages/Edit';
    7
    8const Container = tw.div`text-center`;
    9
    10function App() {
    11 return (
    12 <div>
    13 <GlobalStyles />
    14 <Container>
    15 <Switch>
    16 <Route path='/add'>
    17 <AddPage />
    18 </Route>
    19 <Route path='/edit/:id'>
    20 <EditPage />
    21 </Route>
    22 <Route path='/'>
    23 <HomePage />
    24 </Route>
    25 </Switch>
    26 </Container>
    27 </div>
    28 );
    29}
    30
    31export default App;

    Demo

    Hasil akhir setelah update UI dengan mengganti CSS dengan utility class dari tailwindcss.

    Final code dapat ditemukan di github devsaurus.