@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.
Files changed (158) hide show
  1. package/.babelrc +12 -0
  2. package/.turbo/turbo-check-types.log +2 -0
  3. package/.turbo/turbo-lint.log +12 -0
  4. package/.turbo/turbo-test.log +4084 -0
  5. package/.vscode/settings.json +4 -0
  6. package/eslint.config.mjs +4 -0
  7. package/jest.config.json +10 -0
  8. package/jest.setup.js +2 -0
  9. package/package.json +53 -0
  10. package/src/components/Accordion/Accordion.css.ts +29 -0
  11. package/src/components/Accordion/Accordion.spec.tsx +6 -0
  12. package/src/components/Accordion/Accordion.tsx +44 -0
  13. package/src/components/Accordion/AccordionContent.css.ts +29 -0
  14. package/src/components/Accordion/AccordionContent.tsx +87 -0
  15. package/src/components/Accordion/AccordionContext.ts +9 -0
  16. package/src/components/Accordion/AccordionTrigger.css.ts +46 -0
  17. package/src/components/Accordion/AccordionTrigger.tsx +41 -0
  18. package/src/components/Accordion/index.ts +3 -0
  19. package/src/components/Alert/index.tsx +25 -0
  20. package/src/components/Box/Box.css.ts +18 -0
  21. package/src/components/Box/Box.spec.tsx +6 -0
  22. package/src/components/Box/index.tsx +41 -0
  23. package/src/components/Button/Button.css.ts +241 -0
  24. package/src/components/Button/Button.spec.tsx +30 -0
  25. package/src/components/Button/index.tsx +60 -0
  26. package/src/components/Card/Card.css.ts +93 -0
  27. package/src/components/Card/Card.spec.tsx +24 -0
  28. package/src/components/Card/Card.tsx +41 -0
  29. package/src/components/Card/CardContent.css.ts +8 -0
  30. package/src/components/Card/CardContent.tsx +23 -0
  31. package/src/components/Card/CardInteraction.css.ts +11 -0
  32. package/src/components/Card/CardInteraction.tsx +36 -0
  33. package/src/components/Card/CardThumbnail.css.ts +6 -0
  34. package/src/components/Card/CardThumbnail.tsx +23 -0
  35. package/src/components/Card/index.ts +4 -0
  36. package/src/components/Chip/Chip.css.ts +75 -0
  37. package/src/components/Chip/Chip.spec.tsx +6 -0
  38. package/src/components/Chip/Chip.tsx +37 -0
  39. package/src/components/Chip/index.ts +1 -0
  40. package/src/components/Confirm/index.tsx +44 -0
  41. package/src/components/Dialog/Dialog.css.ts +25 -0
  42. package/src/components/Dialog/Dialog.spec.tsx +26 -0
  43. package/src/components/Dialog/Dialog.tsx +30 -0
  44. package/src/components/Dialog/DialogContent.css.ts +16 -0
  45. package/src/components/Dialog/DialogContent.tsx +26 -0
  46. package/src/components/Dialog/DialogFooter.css.ts +20 -0
  47. package/src/components/Dialog/DialogFooter.tsx +26 -0
  48. package/src/components/Dialog/DialogHeader.css.ts +31 -0
  49. package/src/components/Dialog/DialogHeader.tsx +37 -0
  50. package/src/components/Dialog/index.ts +4 -0
  51. package/src/components/Navigation/Navigation.spec.tsx +19 -0
  52. package/src/components/Navigation/NavigationAside.css.ts +7 -0
  53. package/src/components/Navigation/NavigationAside.tsx +23 -0
  54. package/src/components/Navigation/NavigationBar.css.ts +42 -0
  55. package/src/components/Navigation/NavigationBar.tsx +25 -0
  56. package/src/components/Navigation/NavigationContainer.css.ts +11 -0
  57. package/src/components/Navigation/NavigationContainer.tsx +26 -0
  58. package/src/components/Navigation/NavigationDrawer.css.ts +61 -0
  59. package/src/components/Navigation/NavigationDrawer.tsx +67 -0
  60. package/src/components/Navigation/NavigationItem.css.ts +43 -0
  61. package/src/components/Navigation/NavigationItem.tsx +24 -0
  62. package/src/components/Navigation/NavigationLogo.css.ts +5 -0
  63. package/src/components/Navigation/NavigationLogo.tsx +28 -0
  64. package/src/components/Navigation/NavigationMenu.css.ts +23 -0
  65. package/src/components/Navigation/NavigationMenu.tsx +25 -0
  66. package/src/components/Navigation/index.ts +7 -0
  67. package/src/components/Range/Range.css.ts +132 -0
  68. package/src/components/Range/Range.spec.tsx +6 -0
  69. package/src/components/Range/Range.tsx +90 -0
  70. package/src/components/Range/index.ts +1 -0
  71. package/src/components/ScrollArea/ScrollArea.css.ts +40 -0
  72. package/src/components/ScrollArea/ScrollArea.spec.tsx +6 -0
  73. package/src/components/ScrollArea/ScrollArea.tsx +68 -0
  74. package/src/components/ScrollArea/index.ts +1 -0
  75. package/src/components/Select/Select.css.ts +22 -0
  76. package/src/components/Select/Select.spec.tsx +65 -0
  77. package/src/components/Select/Select.tsx +111 -0
  78. package/src/components/Select/SelectContext.ts +59 -0
  79. package/src/components/Select/SelectOption.css.ts +14 -0
  80. package/src/components/Select/SelectOption.tsx +40 -0
  81. package/src/components/Select/SelectOptionList.css.ts +68 -0
  82. package/src/components/Select/SelectOptionList.tsx +59 -0
  83. package/src/components/Select/SelectTrigger.css.ts +73 -0
  84. package/src/components/Select/SelectTrigger.tsx +49 -0
  85. package/src/components/Select/index.tsx +2 -0
  86. package/src/components/Skeleton/Skeleton.css.ts +26 -0
  87. package/src/components/Skeleton/Skeleton.spec.tsx +6 -0
  88. package/src/components/Skeleton/index.tsx +27 -0
  89. package/src/components/Table/Table.css.ts +10 -0
  90. package/src/components/Table/Table.spec.tsx +12 -0
  91. package/src/components/Table/Table.tsx +27 -0
  92. package/src/components/Table/TableBody.tsx +14 -0
  93. package/src/components/Table/TableCell.css.ts +43 -0
  94. package/src/components/Table/TableCell.tsx +30 -0
  95. package/src/components/Table/TableHead.css.ts +10 -0
  96. package/src/components/Table/TableHead.tsx +30 -0
  97. package/src/components/Table/TableHeader.tsx +14 -0
  98. package/src/components/Table/TableRow.css.ts +3 -0
  99. package/src/components/Table/TableRow.tsx +24 -0
  100. package/src/components/Table/index.ts +6 -0
  101. package/src/components/Tabs/Tabs.spec.tsx +46 -0
  102. package/src/components/Tabs/Tabs.tsx +34 -0
  103. package/src/components/Tabs/TabsContent.tsx +32 -0
  104. package/src/components/Tabs/TabsList.css.ts +11 -0
  105. package/src/components/Tabs/TabsList.tsx +25 -0
  106. package/src/components/Tabs/TabsProvider.tsx +17 -0
  107. package/src/components/Tabs/TabsTrigger.css.ts +38 -0
  108. package/src/components/Tabs/TabsTrigger.tsx +43 -0
  109. package/src/components/Tabs/index.ts +4 -0
  110. package/src/components/TextField/TextField.css.ts +81 -0
  111. package/src/components/TextField/TextField.spec.tsx +6 -0
  112. package/src/components/TextField/index.tsx +38 -0
  113. package/src/components/Toast/Toast.css.ts +79 -0
  114. package/src/components/Toast/Toast.spec.tsx +6 -0
  115. package/src/components/Toast/index.tsx +48 -0
  116. package/src/components/Typography/Typography.css.ts +17 -0
  117. package/src/components/Typography/Typography.spec.tsx +35 -0
  118. package/src/components/Typography/index.tsx +57 -0
  119. package/src/components/index.ts +18 -0
  120. package/src/contexts/UIProvider.tsx +30 -0
  121. package/src/contexts/index.ts +1 -0
  122. package/src/hooks/index.ts +5 -0
  123. package/src/hooks/useDialog/index.tsx +51 -0
  124. package/src/hooks/useDialog/useDialog.spec.tsx +80 -0
  125. package/src/hooks/useMouseScroll/index.ts +63 -0
  126. package/src/hooks/usePointerSlider/index.ts +79 -0
  127. package/src/hooks/useRipple/index.tsx +152 -0
  128. package/src/hooks/useRipple/ripple.css.ts +40 -0
  129. package/src/hooks/useToast/ToastContainer.css.ts +12 -0
  130. package/src/hooks/useToast/ToastContainer.tsx +11 -0
  131. package/src/hooks/useToast/ToastProvider.tsx +131 -0
  132. package/src/hooks/useToast/index.ts +15 -0
  133. package/src/index.ts +8 -0
  134. package/src/styles/globalStyle.css.ts +36 -0
  135. package/src/styles/index.ts +4 -0
  136. package/src/styles/layers.css.ts +4 -0
  137. package/src/styles/overlay.css.ts +40 -0
  138. package/src/styles/sprinkles.css.ts +149 -0
  139. package/src/styles/sx.ts +13 -0
  140. package/src/tests/uiTest.tsx +54 -0
  141. package/src/themes/darkTheme.css.ts +30 -0
  142. package/src/themes/index.ts +3 -0
  143. package/src/themes/lightTheme.css.ts +30 -0
  144. package/src/themes/theme.css.ts +32 -0
  145. package/src/tokens/index.ts +5 -0
  146. package/src/tokens/scale/color.ts +604 -0
  147. package/src/tokens/semantic/breakpoint.ts +6 -0
  148. package/src/tokens/semantic/color.ts +10 -0
  149. package/src/tokens/semantic/spacing.ts +9 -0
  150. package/src/tokens/semantic/typography.ts +32 -0
  151. package/src/types/index.ts +1 -0
  152. package/src/types/ui.types.ts +26 -0
  153. package/src/utils/index.ts +1 -0
  154. package/src/utils/sprinklesUtils.ts +28 -0
  155. package/src/utils/styleUtils.css.ts +109 -0
  156. package/tsconfig.json +11 -0
  157. package/turbo/generators/config.ts +30 -0
  158. 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,4 @@
1
+ export * from './Dialog';
2
+ export * from './DialogContent';
3
+ export * from './DialogFooter';
4
+ export * from './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,7 @@
1
+ import { styleWithLayer } from '#styleUtils';
2
+
3
+ export const wide = styleWithLayer({
4
+ display: 'flex',
5
+ alignItems: 'center',
6
+ gap: '0.5em',
7
+ });
@@ -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,11 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ export const container = style({
4
+ display: 'flex',
5
+ alignItems: 'center',
6
+ justifyContent: 'space-between',
7
+ gap: '0.5em',
8
+
9
+ height: '100%',
10
+ margin: '0 auto',
11
+ });
@@ -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
+ });