Package Exports
- @black-ui/react
- @black-ui/react/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@black-ui/react) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Vanilla Extract 기반 디자인 시스템으로 성능 저하 없이 멋진 디자인을 구현하세요!
미완성 프로젝트입니다. 개선할 부분이 있다면 언제든지 Issues 남겨주세요!
Table of Contents
- 📦 Getting Started
- 🛠️ Stack
- 📒 Storybook
- 🔮 Todo
- 💻 Components
- Hooks
Stack (배경이 궁금하다면)
| 기술 | 설명 |
|---|---|
| TypeScript | JavaScript의 확장 언어 |
| React | JavaScript 프레임워크 |
| Vite | React 개발을 위한 빌드 도구 |
| Vanilla Extract | Zero-runtime Stylesheets in TypeScript. |
| Storybook | React 컴포넌트를 테스트하고 문서화하는 도구 |
| Jest | JavaScript 테스트 프레임워크 |
Getting Started
Introduction
black-ui는 react + typescript + vanilla-extract 조합으로 개발한 디자인 시스템입니다.
기존의 유명 디자인 시스템들은 emotion 같은 css-in-js 라이브러리로 개발된 라이브러리들이 많았습니다.
하지만 일반적인 css-in-js 라이브러리들은 js가 css로 변환되는 과정이 런타임 단계에서 일어나기 때문에 성능적인 이슈가 발생할 수 있습니다.
black-ui는 이러한 점을 보완하기 위해 vanilla-extract 라는 스타일링 라이브러리를 선택했습니다.
vanilla-extract도 css-in-js 라이브러리 이지만 css 변환 과정이 런타임이 아니라 컴파일 타임 때 일어나기 때문에 성능적인 이점을 가질 수 있습니다.
기존 디자인 시스템보다 빠르고 완벽한 타입 추론을 지원하는 black-ui를 사용해보세요!
Installation
npm i @black-ui/reactTheming
black-ui를 사용하기 위해서는 반드시 최상위 컴포넌트를 ThemePovider 컴포넌트로 감싸야합니다.
기본 테마는 light이지만 defaultMode props로 기본 테마를 변경할 수 있습니다.
<ThemeProvider defaultMode="light">
<App />
</ThemeProvider>또한 ThemeSwitcher 컴포넌트를 통해 테마를 자유롭게 변경할 수 있습니다.
<ThemeSwitcher></ThemeSwitcher>Storybook - Docs
Storybook으로 배포한 Black UI 컴포넌트들을 직접 사용해볼 수 있어요!
Todo
- 스타일 가이드 작성하기
- 테스트 코드 보완하기
- SSR 대응하기
- 반응형 디자인 구현
- 웹 접근성 높이기 WCAG
- Headless 컴포넌트 추가
- Context API 최적화
- 번들 사이즈 최적화
- 컴포넌트 단위로 패키지 분할
- Figma 연동
- 공통 로직 분리하기
- 컴포넌트 추가 구현하기
- Carousel
- Calendar
- Date Picker
Components
DataDisplay
Avatar - Source
Import
import { Avatar } from "@black-ui/react";Usage
export const Example = () => {
return <Avatar src="/images/profile.jpg" alt="Name" size="sm" />;
};Card - Source
Import
import { Card, CardHeader, CardBody, CardFooter } from "@black-ui/react";Usage
export const Example = () => {
return (
<Card variant="elevated">
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>
);
};List - Source
Import
import { List, ListItem, ListIcon } from "@black-ui/react";Usage
export const Example = () => {
return (
<List>
<ListItem>
<ListIcon as={<IoMdSettings />} color="green" />
Lorem ipsum dolor sit amet, consectetur adipisicing elit
</ListItem>
<ListItem>
<ListIcon as={<IoMdSettings />} color="green" />
Assumenda, quia temporibus eveniet a libero incidunt suscipit
</ListItem>
<ListItem>
<ListIcon as={<IoMdSettings />} color="green" />
Quidem, ipsam illum quis sed voluptatum quae eum fugit earum
</ListItem>
<ListItem>
<ListIcon as={<IoMdSettings />} color="green" />
Lorem ipsum dolor sit amet, consectetur adipisicing elit
</ListItem>
</List>
);
};Table - Source
Import
import {
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Tfoot,
Th,
Thead,
Tr,
} from "@black-ui/react";Usage
export const Example = () => {
return (
<TableContainer>
<Table variant="filled">
<TableCaption>Imperial to metric conversion factors</TableCaption>
<Thead>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th>multiply by</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>inches</Td>
<Td>millimetres (mm)</Td>
<Td>25.4</Td>
</Tr>
<Tr>
<Td>feet</Td>
<Td>centimetres (cm)</Td>
<Td>30.48</Td>
</Tr>
<Tr>
<Td>yards</Td>
<Td>metres (m)</Td>
<Td>0.91444</Td>
</Tr>
</Tbody>
<Tfoot>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th>multiply by</Th>
</Tr>
</Tfoot>
</Table>
</TableContainer>
);
};Tag - Source
Import
import { Tag, TagIcon, TagLabel } from "@black-ui/react";Usage
export const Example = () => {
return (
<>
<Tag>Sample Tag</Tag>
<Tag color="red" variant="solid">
<TagIcon as={<IoMdSettings />} />
<TagLabel>Left Icon</TagLabel>
</Tag>
<Tag color="blue" variant="subtle">
<TagLabel>Right Icon</TagLabel>
<TagIcon as={<IoMdSettings />} />
</Tag>
<Tag>
<TagLabel>Close Tag</TagLabel>
</Tag>
</>
);
};Disclosure
Accordion - Source
Import
import {
Accordion,
AccordionButton,
AccordionItem,
AccordionPanel,
} from "@black-ui/react";Usage
export const Example = () => {
return (
<Accordion>
<AccordionItem>
<AccordionButton>First Title</AccordionButton>
<AccordionPanel>First Contents</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton>Second Title</AccordionButton>
<AccordionPanel>Second Contents</AccordionPanel>
</AccordionItem>
</Accordion>
);
};Tabs - Source
Import
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@black-ui/react";Usage
export const Example = () => {
return (
<Tabs variant="soft-rounded">
<TabList>
<Tab>First Tab</Tab>
<Tab>Second Tab</Tab>
</TabList>
<TabPanels>
<TabPanel>First Panel</TabPanel>
<TabPanel>Second Panel</TabPanel>
</TabPanels>
</Tabs>
);
};VisuallyHidden - Source
Import
import { VisuallyHidden, VisuallyHiddenInput } from "@black-ui/react";Usage
export const Example = () => {
return (
<>
<VisuallyHidden>Hello</VisuallyHidden>
<VisuallyHiddenInput />
</>
);
};Feedback
Progress - Source
Import
import { Progress } from "@black-ui/react";Usage
export const Example = () => {
const [value, setValue] = useState(0);
return <Progress value={value} />;
};Skeleton - Source
Import
import { Skeleton } from "@black-ui/react";Usage
export const Example = () => {
return (
<Skeleton width="150px" height="150px" radius="15px" background="gray" />
);
};Spinner - Source
Import
import { Spinner } from "@black-ui/react";Usage
export const Example = () => {
return <Spinner size="sm" />;
};Toast - Source
Import
import { ToastProvider, useToast, Button } from "@black-ui/react";Usage
export const Example = () => {
const { openToast } = useToast();
return (
<ToastProvider>
<Button
onClick={() =>
openToast({
title: "Title",
description: "Description",
status: "success",
})
}
>
Toast
</Button>
</ToastProvider>
);
};Form
Button - Source
Import
import { Button, IconButton } from "@black-ui/react";Usage
export const Example = () => {
return (
<>
<Button size="lg" variant="solid" color="black" leftIcon={<IoMdClose />}>
Button
</Button>
<Button size="lg" variant="outline" color="red" rightIcon={<IoMdClose />}>
Button
</Button>
<Button
size="lg"
variant="solid"
color="black"
onClick={() => alert("블랙 클릭")}
leftIcon={<IoMdClose />}
isLoading
spinner={<IoIosArrowDown />}
>
Button
</Button>
<Button
size="lg"
variant="solid"
color="black"
onClick={() => alert("블랙 클릭")}
leftIcon={<IoMdClose />}
isLoading
loadingText="loading..."
spinnerPlacement="right"
>
Button
</Button>
<Button
size="lg"
variant="outline"
color="red"
onClick={() => alert("레드 클릭")}
>
<IoMdClose />
Button
</Button>
<Button
size="lg"
variant="outline"
color="red"
isDisabled
onClick={() => alert("레드 클릭")}
>
Button
</Button>
<IconButton icon={<IoMdStar />} aria-label="Star" isLoading />
</>
);
};Checkbox - Source
Import
import { Checkbox } from "@black-ui/react";Usage
export const Example = () => {
return (
<>
<Checkbox color="black" size="xs">
XS Checkbox
</Checkbox>
<Checkbox color="red" size="sm" disabled>
SM Checkbox
</Checkbox>
<Checkbox color="red" size="md" defaultChecked>
MD Checkbox
</Checkbox>
<Checkbox color="red" size="lg" readOnly>
LG Checkbox
</Checkbox>
</>
);
};FormControl - Source
Import
import {
FormControl,
FormLabel,
FormHelperText,
FormErrorMessage,
Input,
} from "@black-ui/react";Usage
export const Example = () => {
return (
<FormControl>
<FormLabel>Email</FormLabel>
<Input />
<FormHelperText>
Enter the email you'd like to receive the newsletter on.
</FormHelperText>
<FormErrorMessage>Email is required.</FormErrorMessage>
</FormControl>
);
};Input - Source
Import
import { Input } from "@black-ui/react";Usage
export const Example = () => {
return (
<>
<Input placeholder="아이디를 입력해라" size="xs" variant="outline" />
<Input placeholder="아이디를 입력해라" size="xs" variant="filled" />
<Input placeholder="아이디를 입력해라" size="xs" variant="flushed" />
<Input placeholder="아이디를 입력해라" size="xs" variant="unstyled" />
</>
);
};PinInput - Source
Import
import { PinInput, PinInputField } from "@black-ui/react";Usage
export const Example = () => {
return (
<PinInput size="lg" mask>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
);
};Radio - Source
Import
import { RadioGroup, Radio } from "@black-ui/react";Usage
export const Example = () => {
const [radioValue, setRadioValue] = useState("");
const changeRadio = (value: string) => {
setRadioValue(value);
};
return (
<RadioGroup onChange={changeRadio} direction="row">
<Radio color="black" size="xs" value={1}>
XS Radio
</Radio>
<Radio color="black" size="sm" value={2}>
SM Radio
</Radio>
<Radio color="black" size="md" value={3}>
MD Radio
</Radio>
<Radio color="red" size="lg" value={4}>
LG Radio
</Radio>
</RadioGroup>
);
};CustomSelect - Source
Import
import {
CustomSelect,
CustomSelectTrigger,
CustomSelectContent,
CustomSelectGroup,
CustomSelectLabel,
CustomSelectItem,
} from "@black-ui/react";Usage
export const Example = () => {
return (
<CustomSelect size="md" variant="outline" label="과일을 선택해주세요.">
<CustomSelectTrigger></CustomSelectTrigger>
<CustomSelectContent>
<CustomSelectGroup>
<CustomSelectLabel>Fruits</CustomSelectLabel>
<CustomSelectItem value="apple">Apple</CustomSelectItem>
<CustomSelectItem value="banana">Banana</CustomSelectItem>
<CustomSelectItem value="blueberry">Blueberry</CustomSelectItem>
</CustomSelectGroup>
</CustomSelectContent>
</CustomSelect>
);
};Slider - Source
Import
import { Slider } from "@black-ui/react";Usage
export const Example = () => {
const [value, setValue] = useState(0);
return (
<Slider
color="red"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
};Switch - Source
Import
import { Switch } from "@black-ui/react";Usage
export const Example = () => {
const [value, setValue] = useState("");
return (
<Switch
size="xs"
color="red"
value={value}
onChage={(e) => value(e.target.value)}
>
Red
</Switch>
);
};Textarea - Source
Import
import { Textarea } from "@black-ui/react";Usage
export const Example = () => {
const [value, setValue] = useState("");
return (
<Textarea
placeholder="Here is a sample placeholder"
size="sm"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
};Overlay
Drawer - Source
Import
import {
Drawer,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
DrawerHeader,
DrawerBody,
DrawerFooter,
useDisclosure,
} from "@black-ui/react";Usage
export const Example = () => {
const {
isOpen: isDrawerOpen,
onOpen: onDrawerOpen,
onClose: onDrawerClose,
} = useDisclosure();
return (
<Drawer isOpen={isDrawerOpen} onClose={onDrawerClose} placement={placement}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Header</DrawerHeader>
<DrawerBody>Body</DrawerBody>
<DrawerFooter>Footer</DrawerFooter>
</DrawerContent>
</Drawer>
);
};Menu - Source
Import
import { Menu, MenuButton, MenuList, MenuItem } from "@black-ui/react";Usage
export const Example = () => {
return (
<Menu>
<MenuButton>Menu 나와라!</MenuButton>
<MenuList>
<MenuItem
onClick={() => {
alert("다운로드!");
}}
>
Download
</MenuItem>
<MenuItem>Create a Copy</MenuItem>
<MenuItem>Mark as Draft</MenuItem>
<MenuItem>Delete</MenuItem>
<MenuItem>Attend a Workshop</MenuItem>
</MenuList>
</Menu>
);
};Modal - Source
Import
import {
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@black-ui/react";Usage
export const Example = () => {
const {
isOpen: isModalOpen,
onOpen: onModalOpen,
onClose: onModalClose,
} = useDisclosure();
return (
<>
<Button onClick={onModalOpen}>Modal 나와라!</Button>
<Modal isOpen={isModalOpen} onClose={onModalClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Modal Title</ModalHeader>
<ModalCloseButton />
<ModalBody>
<div>Modal 입니다!</div>
</ModalBody>
<ModalFooter>
<Button onClick={() => onModalClose()}>취소</Button>
<Button onClick={() => onModalClose()}>확인</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};Popover - Source
Import
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverCloseButton,
Button,
} from "@black-ui/react";Usage
export const Example = () => {
return (
<Popover>
<PopoverTrigger>
<Button>Popover 나와라!</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<div>Popover입니다!!</div>
</PopoverContent>
</Popover>
);
};Tooltip - Source
Import
import { Tooltip } from "@black-ui/react";Usage
export const Example = () => {
return (
<Tooltip label="Hover me">
<Button>Tooltip 나와라!</Button>
</Tooltip>
);
};Other
CloseButton - Source
Import
import { CloseButton } from "@black-ui/react";Usage
export const Example = () => {
return <CloseButton size="sm" />;
};Portal - Source
Import
import { Portal } from "@black-ui/react";Usage
export const Example = () => {
return (
<Portal>
<div>This text is portaled at the end of document.body!</div>
</Portal>
);
};Theme - Source
Import
import { ThemeProvider, ThemeSwitcher } from "@black-ui/react";Usage
export const Example = () => {
return (
<ThemeProvider defaultMode="light">
<ThemeSwitcher></ThemeSwitcher>
</ThemeProvider>
);
};Hooks
useDisclosure - Source
Import
import { useDisclosure } from "@black-ui/react";Usage
export const Example = () => {
const {
isOpen: isModalOpen,
onOpen: onModalOpen,
onClose: onModalClose,
} = useDisclosure();
return (
<>
<Button onClick={onModalOpen}>Modal 나와라!</Button>
<Modal isOpen={isModalOpen} onClose={onModalClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Modal Title</ModalHeader>
<ModalCloseButton />
<ModalBody>
<div>Modal 입니다!</div>
</ModalBody>
<ModalFooter>
<Button onClick={() => onModalClose()}>취소</Button>
<Button onClick={() => onModalClose()}>확인</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};useClipboard - Source
Import
import { useClipboard } from "@black-ui/react";Usage
export const Example = () => {
const { onCopy, value, setValue, hasCopied } = useClipboard("");
return (
<>
<Input
placeholder={"내용이 복사됩니다."}
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<Button onClick={onCopy}>{hasCopied ? "Copied!" : "Copy"}</Button>
</>
);
};useOutsideClick - Source
Import
import { useOutsideClick } from "@black-ui/react";Usage
function Example() {
const ref = React.useRef();
const [isModalOpen, setIsModalOpen] = React.useState(false);
useOutsideClick({
ref: ref,
handler: () => setIsModalOpen(false),
});
return (
<>
{isModalOpen ? (
<div ref={ref}>
👋 Hey, I'm a modal. Click anywhere outside of me to close.
</div>
) : (
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
)}
</>
);
}