@kimdw-rtk/ui 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +12 -0
- package/.turbo/turbo-check-types.log +2 -0
- package/.turbo/turbo-lint.log +12 -0
- package/.turbo/turbo-test.log +4084 -0
- package/.vscode/settings.json +4 -0
- package/eslint.config.mjs +4 -0
- package/jest.config.json +10 -0
- package/jest.setup.js +2 -0
- package/package.json +53 -0
- package/src/components/Accordion/Accordion.css.ts +29 -0
- package/src/components/Accordion/Accordion.spec.tsx +6 -0
- package/src/components/Accordion/Accordion.tsx +44 -0
- package/src/components/Accordion/AccordionContent.css.ts +29 -0
- package/src/components/Accordion/AccordionContent.tsx +87 -0
- package/src/components/Accordion/AccordionContext.ts +9 -0
- package/src/components/Accordion/AccordionTrigger.css.ts +46 -0
- package/src/components/Accordion/AccordionTrigger.tsx +41 -0
- package/src/components/Accordion/index.ts +3 -0
- package/src/components/Alert/index.tsx +25 -0
- package/src/components/Box/Box.css.ts +18 -0
- package/src/components/Box/Box.spec.tsx +6 -0
- package/src/components/Box/index.tsx +41 -0
- package/src/components/Button/Button.css.ts +241 -0
- package/src/components/Button/Button.spec.tsx +30 -0
- package/src/components/Button/index.tsx +60 -0
- package/src/components/Card/Card.css.ts +93 -0
- package/src/components/Card/Card.spec.tsx +24 -0
- package/src/components/Card/Card.tsx +41 -0
- package/src/components/Card/CardContent.css.ts +8 -0
- package/src/components/Card/CardContent.tsx +23 -0
- package/src/components/Card/CardInteraction.css.ts +11 -0
- package/src/components/Card/CardInteraction.tsx +36 -0
- package/src/components/Card/CardThumbnail.css.ts +6 -0
- package/src/components/Card/CardThumbnail.tsx +23 -0
- package/src/components/Card/index.ts +4 -0
- package/src/components/Chip/Chip.css.ts +75 -0
- package/src/components/Chip/Chip.spec.tsx +6 -0
- package/src/components/Chip/Chip.tsx +37 -0
- package/src/components/Chip/index.ts +1 -0
- package/src/components/Confirm/index.tsx +44 -0
- package/src/components/Dialog/Dialog.css.ts +25 -0
- package/src/components/Dialog/Dialog.spec.tsx +26 -0
- package/src/components/Dialog/Dialog.tsx +30 -0
- package/src/components/Dialog/DialogContent.css.ts +16 -0
- package/src/components/Dialog/DialogContent.tsx +26 -0
- package/src/components/Dialog/DialogFooter.css.ts +20 -0
- package/src/components/Dialog/DialogFooter.tsx +26 -0
- package/src/components/Dialog/DialogHeader.css.ts +31 -0
- package/src/components/Dialog/DialogHeader.tsx +37 -0
- package/src/components/Dialog/index.ts +4 -0
- package/src/components/Navigation/Navigation.spec.tsx +19 -0
- package/src/components/Navigation/NavigationAside.css.ts +7 -0
- package/src/components/Navigation/NavigationAside.tsx +23 -0
- package/src/components/Navigation/NavigationBar.css.ts +42 -0
- package/src/components/Navigation/NavigationBar.tsx +25 -0
- package/src/components/Navigation/NavigationContainer.css.ts +11 -0
- package/src/components/Navigation/NavigationContainer.tsx +26 -0
- package/src/components/Navigation/NavigationDrawer.css.ts +61 -0
- package/src/components/Navigation/NavigationDrawer.tsx +67 -0
- package/src/components/Navigation/NavigationItem.css.ts +43 -0
- package/src/components/Navigation/NavigationItem.tsx +24 -0
- package/src/components/Navigation/NavigationLogo.css.ts +5 -0
- package/src/components/Navigation/NavigationLogo.tsx +28 -0
- package/src/components/Navigation/NavigationMenu.css.ts +23 -0
- package/src/components/Navigation/NavigationMenu.tsx +25 -0
- package/src/components/Navigation/index.ts +7 -0
- package/src/components/Range/Range.css.ts +132 -0
- package/src/components/Range/Range.spec.tsx +6 -0
- package/src/components/Range/Range.tsx +90 -0
- package/src/components/Range/index.ts +1 -0
- package/src/components/ScrollArea/ScrollArea.css.ts +40 -0
- package/src/components/ScrollArea/ScrollArea.spec.tsx +6 -0
- package/src/components/ScrollArea/ScrollArea.tsx +68 -0
- package/src/components/ScrollArea/index.ts +1 -0
- package/src/components/Select/Select.css.ts +22 -0
- package/src/components/Select/Select.spec.tsx +65 -0
- package/src/components/Select/Select.tsx +111 -0
- package/src/components/Select/SelectContext.ts +59 -0
- package/src/components/Select/SelectOption.css.ts +14 -0
- package/src/components/Select/SelectOption.tsx +40 -0
- package/src/components/Select/SelectOptionList.css.ts +68 -0
- package/src/components/Select/SelectOptionList.tsx +59 -0
- package/src/components/Select/SelectTrigger.css.ts +73 -0
- package/src/components/Select/SelectTrigger.tsx +49 -0
- package/src/components/Select/index.tsx +2 -0
- package/src/components/Skeleton/Skeleton.css.ts +26 -0
- package/src/components/Skeleton/Skeleton.spec.tsx +6 -0
- package/src/components/Skeleton/index.tsx +27 -0
- package/src/components/Table/Table.css.ts +10 -0
- package/src/components/Table/Table.spec.tsx +12 -0
- package/src/components/Table/Table.tsx +27 -0
- package/src/components/Table/TableBody.tsx +14 -0
- package/src/components/Table/TableCell.css.ts +43 -0
- package/src/components/Table/TableCell.tsx +30 -0
- package/src/components/Table/TableHead.css.ts +10 -0
- package/src/components/Table/TableHead.tsx +30 -0
- package/src/components/Table/TableHeader.tsx +14 -0
- package/src/components/Table/TableRow.css.ts +3 -0
- package/src/components/Table/TableRow.tsx +24 -0
- package/src/components/Table/index.ts +6 -0
- package/src/components/Tabs/Tabs.spec.tsx +46 -0
- package/src/components/Tabs/Tabs.tsx +34 -0
- package/src/components/Tabs/TabsContent.tsx +32 -0
- package/src/components/Tabs/TabsList.css.ts +11 -0
- package/src/components/Tabs/TabsList.tsx +25 -0
- package/src/components/Tabs/TabsProvider.tsx +17 -0
- package/src/components/Tabs/TabsTrigger.css.ts +38 -0
- package/src/components/Tabs/TabsTrigger.tsx +43 -0
- package/src/components/Tabs/index.ts +4 -0
- package/src/components/TextField/TextField.css.ts +81 -0
- package/src/components/TextField/TextField.spec.tsx +6 -0
- package/src/components/TextField/index.tsx +38 -0
- package/src/components/Toast/Toast.css.ts +79 -0
- package/src/components/Toast/Toast.spec.tsx +6 -0
- package/src/components/Toast/index.tsx +48 -0
- package/src/components/Typography/Typography.css.ts +17 -0
- package/src/components/Typography/Typography.spec.tsx +35 -0
- package/src/components/Typography/index.tsx +57 -0
- package/src/components/index.ts +18 -0
- package/src/contexts/UIProvider.tsx +30 -0
- package/src/contexts/index.ts +1 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useDialog/index.tsx +51 -0
- package/src/hooks/useDialog/useDialog.spec.tsx +80 -0
- package/src/hooks/useMouseScroll/index.ts +63 -0
- package/src/hooks/usePointerSlider/index.ts +79 -0
- package/src/hooks/useRipple/index.tsx +152 -0
- package/src/hooks/useRipple/ripple.css.ts +40 -0
- package/src/hooks/useToast/ToastContainer.css.ts +12 -0
- package/src/hooks/useToast/ToastContainer.tsx +11 -0
- package/src/hooks/useToast/ToastProvider.tsx +131 -0
- package/src/hooks/useToast/index.ts +15 -0
- package/src/index.ts +8 -0
- package/src/styles/globalStyle.css.ts +36 -0
- package/src/styles/index.ts +4 -0
- package/src/styles/layers.css.ts +4 -0
- package/src/styles/overlay.css.ts +40 -0
- package/src/styles/sprinkles.css.ts +149 -0
- package/src/styles/sx.ts +13 -0
- package/src/tests/uiTest.tsx +54 -0
- package/src/themes/darkTheme.css.ts +30 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/lightTheme.css.ts +30 -0
- package/src/themes/theme.css.ts +32 -0
- package/src/tokens/index.ts +5 -0
- package/src/tokens/scale/color.ts +604 -0
- package/src/tokens/semantic/breakpoint.ts +6 -0
- package/src/tokens/semantic/color.ts +10 -0
- package/src/tokens/semantic/spacing.ts +9 -0
- package/src/tokens/semantic/typography.ts +32 -0
- package/src/types/index.ts +1 -0
- package/src/types/ui.types.ts +26 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/sprinklesUtils.ts +28 -0
- package/src/utils/styleUtils.css.ts +109 -0
- package/tsconfig.json +11 -0
- package/turbo/generators/config.ts +30 -0
- package/turbo/generators/templates/component.hbs +8 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { sx } from '#styles';
|
|
6
|
+
import type { UIComponent } from '#types';
|
|
7
|
+
|
|
8
|
+
import * as s from './Chip.css';
|
|
9
|
+
|
|
10
|
+
type ChipProps = UIComponent<'div', typeof s.chip>;
|
|
11
|
+
|
|
12
|
+
export const Chip = forwardRef<HTMLDivElement, ChipProps>(
|
|
13
|
+
(
|
|
14
|
+
{
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
color = 'primary',
|
|
18
|
+
size = 'md',
|
|
19
|
+
sx: propSx,
|
|
20
|
+
...props
|
|
21
|
+
},
|
|
22
|
+
ref,
|
|
23
|
+
) => {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={clsx(s.chip({ color, size }), className, sx(propSx))}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<span>{children}</span>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
Chip.displayName = 'Chip';
|
|
36
|
+
|
|
37
|
+
export { s as chipCss };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Chip';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useImperativeHandle, type ReactNode, type Ref } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useOverlay, usePreventKeyboardInput } from '@kimdw-rtk/utils';
|
|
6
|
+
|
|
7
|
+
import { Box, Button, Dialog, DialogContent, DialogFooter } from '#components';
|
|
8
|
+
|
|
9
|
+
interface ConfirmProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
ref?: Ref<{ close: () => void }>;
|
|
12
|
+
onConfirm: () => void;
|
|
13
|
+
onCancle: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Confirm = ({
|
|
17
|
+
children,
|
|
18
|
+
ref,
|
|
19
|
+
onConfirm,
|
|
20
|
+
onCancle,
|
|
21
|
+
}: ConfirmProps) => {
|
|
22
|
+
const { close } = useOverlay();
|
|
23
|
+
usePreventKeyboardInput();
|
|
24
|
+
|
|
25
|
+
useImperativeHandle(ref, () => ({
|
|
26
|
+
close,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Dialog>
|
|
31
|
+
<DialogContent>{children}</DialogContent>
|
|
32
|
+
<DialogFooter>
|
|
33
|
+
<Box flex gap="md">
|
|
34
|
+
<Button size="sm" onClick={onConfirm}>
|
|
35
|
+
확인
|
|
36
|
+
</Button>
|
|
37
|
+
<Button color="secondary" size="sm" onClick={onCancle}>
|
|
38
|
+
취소
|
|
39
|
+
</Button>
|
|
40
|
+
</Box>
|
|
41
|
+
</DialogFooter>
|
|
42
|
+
</Dialog>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
|
|
3
|
+
import { theme } from '#themes';
|
|
4
|
+
|
|
5
|
+
export const container = style({
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
gap: '1rem',
|
|
9
|
+
overflowY: 'auto',
|
|
10
|
+
|
|
11
|
+
maxWidth: 'calc(100vw - 2rem)',
|
|
12
|
+
minWidth: 'min(20rem, calc(100vw - 2rem))',
|
|
13
|
+
maxHeight: 'calc(100vh - 2rem)',
|
|
14
|
+
borderRadius: theme.borderRadius,
|
|
15
|
+
boxSizing: 'border-box',
|
|
16
|
+
|
|
17
|
+
backgroundColor: `rgb(${theme.color.background})`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const message = style({
|
|
21
|
+
marginBottom: '1rem',
|
|
22
|
+
|
|
23
|
+
lineHeight: '150%',
|
|
24
|
+
wordBreak: 'break-all',
|
|
25
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '#components';
|
|
4
|
+
|
|
5
|
+
import { uiTest } from '../../tests/uiTest';
|
|
6
|
+
|
|
7
|
+
describe('Dialog 컴포넌트', () => {
|
|
8
|
+
uiTest(Dialog, 'Dialog');
|
|
9
|
+
uiTest(DialogHeader, 'DialogHeader');
|
|
10
|
+
uiTest(DialogContent, 'DialogContent');
|
|
11
|
+
uiTest(DialogFooter, 'DialogFooter');
|
|
12
|
+
|
|
13
|
+
it('DialogHeader의 닫기 버튼을 클릭하면 onCloseClick이 호출되어야 한다', () => {
|
|
14
|
+
const handleCloseClick = jest.fn();
|
|
15
|
+
render(
|
|
16
|
+
<Dialog>
|
|
17
|
+
<DialogHeader onCloseClick={handleCloseClick}>Header</DialogHeader>
|
|
18
|
+
<DialogContent>Content</DialogContent>
|
|
19
|
+
</Dialog>,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
fireEvent.click(screen.getByRole('button', { name: '닫기' }));
|
|
23
|
+
|
|
24
|
+
expect(handleCloseClick).toHaveBeenCalledTimes(1);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { Box } from '#components';
|
|
6
|
+
import { sx } from '#styles';
|
|
7
|
+
import type { UIComponent } from '#types';
|
|
8
|
+
|
|
9
|
+
import * as s from './Dialog.css';
|
|
10
|
+
|
|
11
|
+
type DialogProps = Omit<UIComponent<'div'>, 'color'>;
|
|
12
|
+
|
|
13
|
+
export const Dialog = forwardRef<HTMLDivElement, DialogProps>(
|
|
14
|
+
({ children, className, sx: propSx, ...props }, ref) => {
|
|
15
|
+
return (
|
|
16
|
+
<Box
|
|
17
|
+
ref={ref}
|
|
18
|
+
flex
|
|
19
|
+
flexDirection="row"
|
|
20
|
+
gap="lg"
|
|
21
|
+
boxShadow="border-sm"
|
|
22
|
+
className={clsx(s.container, className, sx(propSx))}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</Box>
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
Dialog.displayName = 'Dialog';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { styleWithLayer } from '#styleUtils';
|
|
2
|
+
import { theme } from '#themes';
|
|
3
|
+
|
|
4
|
+
import * as header from './DialogHeader.css';
|
|
5
|
+
|
|
6
|
+
export const container = styleWithLayer({
|
|
7
|
+
padding: '0 1.5rem 1.5rem 1.5rem',
|
|
8
|
+
|
|
9
|
+
wordBreak: 'break-all',
|
|
10
|
+
|
|
11
|
+
selectors: {
|
|
12
|
+
[`${header.container} + &`]: {
|
|
13
|
+
color: `rgb(${theme.color['muted-foreground']})`,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { Box } from '#components';
|
|
6
|
+
import { sx } from '#styles';
|
|
7
|
+
import type { UIComponent } from '#types';
|
|
8
|
+
|
|
9
|
+
import * as s from './DialogContent.css';
|
|
10
|
+
|
|
11
|
+
type DialogContentProps = Omit<UIComponent<'div'>, 'color'>;
|
|
12
|
+
|
|
13
|
+
export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
|
|
14
|
+
({ children, className, sx: propSx, ...props }, ref) => {
|
|
15
|
+
return (
|
|
16
|
+
<Box
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={clsx(s.container, className, sx(propSx))}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
DialogContent.displayName = 'DialogContent';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { styleWithLayer } from '#styleUtils';
|
|
2
|
+
|
|
3
|
+
export const container = styleWithLayer({
|
|
4
|
+
maxWidth: 'calc(100vw - 2rem)',
|
|
5
|
+
minWidth: 'min(20rem, calc(100vw - 2rem))',
|
|
6
|
+
padding: '1rem',
|
|
7
|
+
borderRadius: '0.25rem',
|
|
8
|
+
boxSizing: 'border-box',
|
|
9
|
+
|
|
10
|
+
backgroundColor: '#fff',
|
|
11
|
+
|
|
12
|
+
userSelect: 'none',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const message = styleWithLayer({
|
|
16
|
+
marginBottom: '1rem',
|
|
17
|
+
|
|
18
|
+
lineHeight: '150%',
|
|
19
|
+
wordBreak: 'break-all',
|
|
20
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { Box } from '#components';
|
|
6
|
+
import { sx } from '#styles';
|
|
7
|
+
import type { UIComponent } from '#types';
|
|
8
|
+
|
|
9
|
+
type DialogFooterProps = Omit<UIComponent<'div'>, 'color'>;
|
|
10
|
+
|
|
11
|
+
export const DialogFooter = forwardRef<HTMLDivElement, DialogFooterProps>(
|
|
12
|
+
({ children, className, sx: propSx, ...props }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<Box
|
|
15
|
+
ref={ref}
|
|
16
|
+
flex
|
|
17
|
+
justifyContent="flex-end"
|
|
18
|
+
className={clsx(className, sx(propSx))}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
DialogFooter.displayName = 'DialogFooter';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { styleWithLayer } from '#styleUtils';
|
|
2
|
+
import { theme } from '#themes';
|
|
3
|
+
|
|
4
|
+
export const container = styleWithLayer({
|
|
5
|
+
position: 'sticky',
|
|
6
|
+
top: '0',
|
|
7
|
+
padding: '1.5rem',
|
|
8
|
+
zIndex: '10',
|
|
9
|
+
|
|
10
|
+
backgroundColor: `rgb(${theme.color.background})`,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const close = styleWithLayer({
|
|
14
|
+
width: '1.25rem',
|
|
15
|
+
height: '1.25rem',
|
|
16
|
+
padding: '0',
|
|
17
|
+
border: '0',
|
|
18
|
+
|
|
19
|
+
backgroundColor: 'transparent',
|
|
20
|
+
|
|
21
|
+
fontSize: '1em',
|
|
22
|
+
color: `rgb(${theme.color['muted-foreground']})`,
|
|
23
|
+
|
|
24
|
+
cursor: 'pointer',
|
|
25
|
+
|
|
26
|
+
transition: 'color 0.1s ease',
|
|
27
|
+
|
|
28
|
+
':hover': {
|
|
29
|
+
color: `rgb(${theme.color['foreground']})`,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { XIcon } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { Box } from '#components';
|
|
7
|
+
import { sx } from '#styles';
|
|
8
|
+
import type { UIComponent } from '#types';
|
|
9
|
+
|
|
10
|
+
import * as s from './DialogHeader.css';
|
|
11
|
+
|
|
12
|
+
interface DialogHeaderProps extends Omit<UIComponent<'div'>, 'color'> {
|
|
13
|
+
onCloseClick?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DialogHeader = forwardRef<HTMLDivElement, DialogHeaderProps>(
|
|
17
|
+
({ children, className, sx: propSx, onCloseClick, ...props }, ref) => {
|
|
18
|
+
return (
|
|
19
|
+
<Box
|
|
20
|
+
ref={ref}
|
|
21
|
+
flex
|
|
22
|
+
justifyContent="space-between"
|
|
23
|
+
alignItems="center"
|
|
24
|
+
fontSize="lg"
|
|
25
|
+
fontWeight="normal"
|
|
26
|
+
className={clsx(s.container, className, sx(propSx))}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<span>{children}</span>
|
|
30
|
+
<button className={s.close} onClick={onCloseClick} aria-label="닫기">
|
|
31
|
+
<XIcon />
|
|
32
|
+
</button>
|
|
33
|
+
</Box>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
DialogHeader.displayName = 'DialogHeader';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NavigationAside,
|
|
3
|
+
NavigationBar,
|
|
4
|
+
NavigationItem,
|
|
5
|
+
NavigationLogo,
|
|
6
|
+
NavigationMenu,
|
|
7
|
+
} from '#components';
|
|
8
|
+
|
|
9
|
+
import { uiTest } from '../../tests/uiTest';
|
|
10
|
+
import { NavigationContainer } from './NavigationContainer';
|
|
11
|
+
|
|
12
|
+
describe('Navigation 컴포넌트', () => {
|
|
13
|
+
uiTest(NavigationAside, 'NavigationAside');
|
|
14
|
+
uiTest(NavigationBar, 'NavigationBar');
|
|
15
|
+
uiTest(NavigationItem, 'NavigationItem');
|
|
16
|
+
uiTest(NavigationLogo, 'NavigationLogo');
|
|
17
|
+
uiTest(NavigationMenu, 'NavigationMenu');
|
|
18
|
+
uiTest(NavigationContainer, 'NavigationContainer');
|
|
19
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
|
|
7
|
+
import { sx } from '#styles';
|
|
8
|
+
import type { UIComponent } from '#types';
|
|
9
|
+
|
|
10
|
+
import * as s from './NavigationAside.css';
|
|
11
|
+
|
|
12
|
+
type NavigationAsideProps = UIComponent<'aside'>;
|
|
13
|
+
|
|
14
|
+
export const NavigationAside = forwardRef<HTMLElement, NavigationAsideProps>(
|
|
15
|
+
({ children, className, sx: propSx, ...props }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<aside ref={ref} className={clsx(className, sx(propSx))} {...props}>
|
|
18
|
+
<div className={s.wide}>{children}</div>
|
|
19
|
+
</aside>
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
NavigationAside.displayName = 'NavigationAside';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createContainer } from '@vanilla-extract/css';
|
|
2
|
+
|
|
3
|
+
import { recipeWithLayer } from '#styleUtils';
|
|
4
|
+
import { theme } from '#themes';
|
|
5
|
+
|
|
6
|
+
export const navigationBarContainer = createContainer();
|
|
7
|
+
|
|
8
|
+
export const navigationBar = recipeWithLayer({
|
|
9
|
+
base: {
|
|
10
|
+
zIndex: '20',
|
|
11
|
+
position: 'sticky',
|
|
12
|
+
top: '0',
|
|
13
|
+
|
|
14
|
+
width: '100%',
|
|
15
|
+
borderBottom: `1px solid rgb(${theme.color['border.weak']})`,
|
|
16
|
+
|
|
17
|
+
backgroundColor: `rgba(${theme.color.background}, 0.75)`,
|
|
18
|
+
backdropFilter: 'blur(2rem) saturate(150%)',
|
|
19
|
+
|
|
20
|
+
color: `rgb(${theme.color.border})`,
|
|
21
|
+
|
|
22
|
+
transition: 'border-bottom-color 0.2s ease',
|
|
23
|
+
|
|
24
|
+
containerType: 'inline-size',
|
|
25
|
+
containerName: navigationBarContainer,
|
|
26
|
+
},
|
|
27
|
+
variants: {
|
|
28
|
+
size: {
|
|
29
|
+
sm: {
|
|
30
|
+
height: '4em',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
md: {
|
|
34
|
+
height: '5em',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
lg: {
|
|
38
|
+
height: '6em',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { sx } from '#styles';
|
|
6
|
+
import type { UIComponent } from '#types';
|
|
7
|
+
|
|
8
|
+
import * as s from './NavigationBar.css';
|
|
9
|
+
|
|
10
|
+
type NavigationBarProps = UIComponent<'nav', typeof s.navigationBar>;
|
|
11
|
+
|
|
12
|
+
export const NavigationBar = forwardRef<HTMLElement, NavigationBarProps>(
|
|
13
|
+
({ className, size = 'md', sx: propSx, ...props }, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<nav
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={clsx(s.navigationBar({ size }), className, sx(propSx))}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
NavigationBar.displayName = 'NavigationBar';
|
|
24
|
+
|
|
25
|
+
export { s as navigationBarCss };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { sx } from '#styles';
|
|
6
|
+
import type { UIComponent } from '#types';
|
|
7
|
+
|
|
8
|
+
import * as s from './NavigationContainer.css';
|
|
9
|
+
|
|
10
|
+
type NavigationContainerProps = UIComponent<'div'>;
|
|
11
|
+
|
|
12
|
+
export const NavigationContainer = forwardRef<
|
|
13
|
+
HTMLDivElement,
|
|
14
|
+
NavigationContainerProps
|
|
15
|
+
>(({ children, className, sx: propSx, ...props }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={clsx(className, s.container, sx(propSx))}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
NavigationContainer.displayName = 'NavigationContainer';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { recipeWithLayer, styleWithLayer } from '#styleUtils';
|
|
2
|
+
import { theme } from '#themes';
|
|
3
|
+
|
|
4
|
+
import { navigationBarContainer } from './NavigationBar.css';
|
|
5
|
+
|
|
6
|
+
export const narrow = styleWithLayer({
|
|
7
|
+
display: 'none',
|
|
8
|
+
|
|
9
|
+
'@container': {
|
|
10
|
+
[`${navigationBarContainer} (max-width: 800px)`]: {
|
|
11
|
+
display: 'block',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const wide = styleWithLayer({
|
|
17
|
+
display: 'flex',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
justifyContent: 'space-between',
|
|
20
|
+
gap: '0.5em',
|
|
21
|
+
flexGrow: '1',
|
|
22
|
+
|
|
23
|
+
'@container': {
|
|
24
|
+
[`${navigationBarContainer} (max-width: 800px)`]: {
|
|
25
|
+
display: 'none',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const popup = recipeWithLayer({
|
|
31
|
+
base: {
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
top: '100%',
|
|
34
|
+
height: 'calc(100vh - 100%)',
|
|
35
|
+
left: '0',
|
|
36
|
+
width: '100%',
|
|
37
|
+
zIndex: '100',
|
|
38
|
+
|
|
39
|
+
maxHeight: '100vh',
|
|
40
|
+
padding: '0.75em',
|
|
41
|
+
borderBottom: `1px solid rgb(${theme.color.border})`,
|
|
42
|
+
|
|
43
|
+
backgroundColor: `rgb(${theme.color.background})`,
|
|
44
|
+
|
|
45
|
+
transition: 'opacity 0.2s ease',
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
variants: {
|
|
49
|
+
isVisible: {
|
|
50
|
+
true: {
|
|
51
|
+
opacity: '1',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
false: {
|
|
55
|
+
opacity: '0',
|
|
56
|
+
|
|
57
|
+
pointerEvents: 'none',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import { AlignJustifyIcon, XIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { Box, Button } from '#components';
|
|
8
|
+
|
|
9
|
+
import * as s from './NavigationDrawer.css';
|
|
10
|
+
|
|
11
|
+
interface NavigationDrawerProps {
|
|
12
|
+
menu: ReactNode;
|
|
13
|
+
aside: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const NavigationDrawer = ({ menu, aside }: NavigationDrawerProps) => {
|
|
17
|
+
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
|
18
|
+
|
|
19
|
+
const handleClick = () => {
|
|
20
|
+
setIsExpanded((prev) => !prev);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (isExpanded) {
|
|
25
|
+
document.body.style.overflow = 'hidden';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
if (isExpanded) {
|
|
30
|
+
document.body.style.overflow = 'auto';
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}, [isExpanded]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<div className={s.wide}>
|
|
38
|
+
{menu}
|
|
39
|
+
{aside}
|
|
40
|
+
</div>
|
|
41
|
+
<div className={s.narrow}>
|
|
42
|
+
<Button
|
|
43
|
+
size="icon-md"
|
|
44
|
+
color="secondary"
|
|
45
|
+
variant="ghost"
|
|
46
|
+
onClick={handleClick}
|
|
47
|
+
>
|
|
48
|
+
{isExpanded ? <XIcon /> : <AlignJustifyIcon />}
|
|
49
|
+
</Button>
|
|
50
|
+
<div className={s.popup({ isVisible: isExpanded })}>
|
|
51
|
+
<Box
|
|
52
|
+
flex
|
|
53
|
+
gap="xl"
|
|
54
|
+
flexDirection="column-reverse"
|
|
55
|
+
alignItems="flex-end"
|
|
56
|
+
paddingY="lg"
|
|
57
|
+
>
|
|
58
|
+
<Box width="100%" onClick={() => setIsExpanded(false)}>
|
|
59
|
+
{menu}
|
|
60
|
+
</Box>
|
|
61
|
+
{aside}
|
|
62
|
+
</Box>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { recipeWithLayer } from '#styleUtils';
|
|
2
|
+
import { theme } from '#themes';
|
|
3
|
+
import { spacing, typography } from '#tokens';
|
|
4
|
+
|
|
5
|
+
import { narrow } from './NavigationDrawer.css';
|
|
6
|
+
|
|
7
|
+
export const container = recipeWithLayer({
|
|
8
|
+
base: {
|
|
9
|
+
position: 'relative',
|
|
10
|
+
|
|
11
|
+
lineHeight: '0',
|
|
12
|
+
padding: spacing.lg,
|
|
13
|
+
|
|
14
|
+
fontSize: '0.9375em',
|
|
15
|
+
fontWeight: typography.weight.semiBold,
|
|
16
|
+
|
|
17
|
+
transition: 'color 0.2s ease',
|
|
18
|
+
|
|
19
|
+
cursor: 'pointer',
|
|
20
|
+
|
|
21
|
+
':hover': {
|
|
22
|
+
color: `rgb(${theme.color.foreground})`,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
selectors: {
|
|
26
|
+
[`${narrow} &`]: {
|
|
27
|
+
padding: `${spacing['2xl']} ${spacing.lg}`,
|
|
28
|
+
|
|
29
|
+
fontSize: '1.125em',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
variants: {
|
|
34
|
+
isSelected: {
|
|
35
|
+
true: {
|
|
36
|
+
color: `rgb(${theme.color['secondary-foreground']})`,
|
|
37
|
+
},
|
|
38
|
+
false: {
|
|
39
|
+
color: `rgba(${theme.color['secondary-foreground']}, 0.5)`,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|