@regardio/react 0.4.7 → 0.5.1

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 (202) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -5
  3. package/dist/{components/background-slideshow.js → background-slideshow/index.js} +2 -11
  4. package/dist/{components/blurry-gradient.js → blurry-gradient/index.js} +15 -9
  5. package/dist/{components/carousel.d.ts → carousel/index.d.ts} +17 -9
  6. package/dist/{components/carousel.js → carousel/index.js} +34 -30
  7. package/dist/{components/countdown.js → countdown/index.js} +2 -11
  8. package/dist/{components/generic-error.js → generic-error/index.js} +1 -1
  9. package/dist/grid/index.d.ts +1196 -0
  10. package/dist/grid/index.js +239 -0
  11. package/dist/heading/index.d.ts +24 -0
  12. package/dist/{components/heading.js → heading/index.js} +15 -34
  13. package/dist/highlight/index.d.ts +13 -0
  14. package/dist/{components/highlight.js → highlight/index.js} +9 -17
  15. package/dist/hooks/{use-current-route-data.js → use-current-route-data/index.js} +1 -1
  16. package/dist/hooks/{use-focus-search.js → use-focus-search/index.js} +1 -1
  17. package/dist/hooks/{use-matches-data.js → use-matches-data/index.js} +1 -1
  18. package/dist/hooks/{use-media-query.js → use-media-query/index.js} +1 -1
  19. package/dist/hooks/{use-mobile.js → use-mobile/index.js} +1 -1
  20. package/dist/hooks/use-nonce/index.d.ts +6 -0
  21. package/dist/hooks/use-nonce/index.js +8 -0
  22. package/dist/hooks/{use-orientation.d.ts → use-orientation/index.d.ts} +1 -1
  23. package/dist/hooks/{use-orientation.js → use-orientation/index.js} +1 -1
  24. package/dist/hooks/{use-user.js → use-user/index.js} +1 -1
  25. package/dist/{components/icon-button.js → icon-button/index.js} +1 -1
  26. package/dist/{components/if.js → if/index.js} +1 -1
  27. package/dist/{components/iframe.js → iframe/index.js} +2 -11
  28. package/dist/{components/link.d.ts → link/index.d.ts} +19 -13
  29. package/dist/{components/link.js → link/index.js} +31 -36
  30. package/dist/list/index.d.ts +69 -0
  31. package/dist/list/index.js +65 -0
  32. package/dist/{components/markdown-container.js → markdown-container/index.js} +3 -67
  33. package/dist/{components/password-input.js → password-input/index.js} +2 -11
  34. package/dist/{components/picture.js → picture/index.js} +2 -11
  35. package/dist/{components/protected-email.d.ts → protected-email/index.d.ts} +1 -1
  36. package/dist/{components/protected-email.js → protected-email/index.js} +1 -1
  37. package/dist/text/index.d.ts +20 -0
  38. package/dist/text/index.js +38 -0
  39. package/dist/utils/author/index.d.ts +3 -0
  40. package/dist/utils/author/index.js +33 -0
  41. package/dist/utils/text/index.d.ts +15 -0
  42. package/dist/utils/text/index.js +73 -0
  43. package/package.json +92 -121
  44. package/src/{stories/BackgroundSlideshow.stories.tsx → background-slideshow/background-slideshow.stories.tsx} +1 -1
  45. package/src/{components → background-slideshow}/background-slideshow.tsx +3 -1
  46. package/src/background-slideshow/index.ts +2 -0
  47. package/src/{stories/BlurryGradient.stories.tsx → blurry-gradient/blurry-gradient.stories.tsx} +1 -1
  48. package/src/{components → blurry-gradient}/blurry-gradient.tsx +14 -8
  49. package/src/blurry-gradient/index.ts +2 -0
  50. package/src/carousel/carousel-content.tsx +16 -0
  51. package/src/carousel/carousel-item.tsx +23 -0
  52. package/src/carousel/carousel-next.tsx +22 -0
  53. package/src/carousel/carousel-previous.tsx +22 -0
  54. package/src/{components/carousel.tsx → carousel/carousel-root.tsx} +8 -78
  55. package/src/carousel/carousel.stories.tsx +89 -0
  56. package/src/carousel/index.parts.ts +5 -0
  57. package/src/carousel/index.ts +4 -0
  58. package/src/{stories/Countdown.stories.tsx → countdown/countdown.stories.tsx} +1 -1
  59. package/src/{components → countdown}/countdown.tsx +3 -7
  60. package/src/countdown/index.ts +1 -0
  61. package/src/{stories/GenericError.stories.tsx → generic-error/generic-error.stories.tsx} +1 -1
  62. package/src/{components → generic-error}/generic-error.tsx +2 -0
  63. package/src/generic-error/index.ts +2 -0
  64. package/src/grid/grid-item.tsx +188 -0
  65. package/src/grid/grid-root.tsx +72 -0
  66. package/src/grid/grid.stories.tsx +236 -0
  67. package/src/grid/index.parts.ts +2 -0
  68. package/src/grid/index.ts +5 -0
  69. package/src/{stories/Heading.stories.tsx → heading/heading.stories.tsx} +1 -1
  70. package/src/{components → heading}/heading.tsx +17 -25
  71. package/src/heading/index.ts +2 -0
  72. package/src/{stories/Highlight.stories.tsx → highlight/highlight.stories.tsx} +1 -1
  73. package/src/{components → highlight}/highlight.tsx +13 -9
  74. package/src/highlight/index.ts +2 -0
  75. package/src/hooks/use-current-route-data/index.ts +1 -0
  76. package/src/hooks/use-focus-search/index.ts +1 -0
  77. package/src/hooks/use-matches-data/index.ts +1 -0
  78. package/src/hooks/use-media-query/index.ts +1 -0
  79. package/src/hooks/use-mobile/index.ts +1 -0
  80. package/src/hooks/use-nonce/index.ts +1 -0
  81. package/src/hooks/use-orientation/index.ts +1 -0
  82. package/src/hooks/use-user/index.ts +2 -0
  83. package/src/{stories/IconButton.stories.tsx → icon-button/icon-button.stories.tsx} +1 -1
  84. package/src/icon-button/index.ts +2 -0
  85. package/src/{stories/If.stories.tsx → if/if.stories.tsx} +1 -1
  86. package/src/if/index.ts +1 -0
  87. package/src/{stories/Iframe.stories.tsx → iframe/iframe.stories.tsx} +1 -1
  88. package/src/{components → iframe}/iframe.tsx +1 -1
  89. package/src/iframe/index.ts +2 -0
  90. package/src/link/index.ts +2 -0
  91. package/src/{stories/Link.stories.tsx → link/link.stories.tsx} +1 -1
  92. package/src/{components → link}/link.tsx +39 -28
  93. package/src/list/index.parts.ts +2 -0
  94. package/src/list/index.ts +4 -0
  95. package/src/list/list-item.tsx +63 -0
  96. package/src/list/list-root-context.ts +21 -0
  97. package/src/list/list-root.tsx +81 -0
  98. package/src/list/list.css +32 -0
  99. package/src/list/list.stories.tsx +119 -0
  100. package/src/list/list.test.tsx +168 -0
  101. package/src/markdown-container/index.ts +2 -0
  102. package/src/{stories/MarkdownContainer.stories.tsx → markdown-container/markdown-container.stories.tsx} +1 -1
  103. package/src/{components → markdown-container}/markdown-container.tsx +3 -1
  104. package/src/password-input/index.ts +2 -0
  105. package/src/{stories/PasswordInput.stories.tsx → password-input/password-input.stories.tsx} +1 -1
  106. package/src/{components → password-input}/password-input.tsx +4 -4
  107. package/src/picture/index.ts +2 -0
  108. package/src/{stories/Picture.stories.tsx → picture/picture.stories.tsx} +1 -1
  109. package/src/{components → picture}/picture.tsx +2 -4
  110. package/src/protected-email/index.ts +2 -0
  111. package/src/{stories/ProtectedEmail.stories.tsx → protected-email/protected-email.stories.tsx} +1 -1
  112. package/src/{components → protected-email}/protected-email.tsx +3 -1
  113. package/src/tailwind.css +10 -0
  114. package/src/text/index.ts +2 -0
  115. package/src/{stories/Text.stories.tsx → text/text.stories.tsx} +1 -1
  116. package/src/text/text.tsx +46 -0
  117. package/src/utils/author/author.tsx +36 -0
  118. package/src/utils/author/index.ts +1 -0
  119. package/src/utils/text/index.ts +1 -0
  120. package/src/utils/text/text.tsx +103 -0
  121. package/dist/components/box.d.ts +0 -20
  122. package/dist/components/box.js +0 -50
  123. package/dist/components/definition-list.d.ts +0 -43
  124. package/dist/components/definition-list.js +0 -89
  125. package/dist/components/heading.d.ts +0 -27
  126. package/dist/components/highlight.d.ts +0 -19
  127. package/dist/components/item.d.ts +0 -70
  128. package/dist/components/item.js +0 -512
  129. package/dist/components/leaflet-map.d.ts +0 -34
  130. package/dist/components/leaflet-map.js +0 -201
  131. package/dist/components/list-item.d.ts +0 -19
  132. package/dist/components/list-item.js +0 -37
  133. package/dist/components/maptiler-map.d.ts +0 -27
  134. package/dist/components/maptiler-map.js +0 -129
  135. package/dist/components/text.d.ts +0 -20
  136. package/dist/components/text.js +0 -45
  137. package/dist/components/unordered-list.d.ts +0 -19
  138. package/dist/components/unordered-list.js +0 -39
  139. package/dist/hooks/use-nonce.d.ts +0 -12
  140. package/dist/hooks/use-nonce.js +0 -13
  141. package/dist/utils/author.d.ts +0 -9
  142. package/dist/utils/author.js +0 -55
  143. package/dist/utils/cn.d.ts +0 -9
  144. package/dist/utils/cn.js +0 -14
  145. package/dist/utils/is-route-active.d.ts +0 -19
  146. package/dist/utils/is-route-active.js +0 -56
  147. package/dist/utils/text.d.ts +0 -24
  148. package/dist/utils/text.js +0 -127
  149. package/src/components/box.tsx +0 -45
  150. package/src/components/definition-list.tsx +0 -90
  151. package/src/components/item.tsx +0 -340
  152. package/src/components/leaflet-map.tsx +0 -294
  153. package/src/components/link.test.tsx +0 -387
  154. package/src/components/list-item.tsx +0 -30
  155. package/src/components/maptiler-map.tsx +0 -181
  156. package/src/components/text.tsx +0 -38
  157. package/src/components/unordered-list.tsx +0 -32
  158. package/src/hooks/use-nonce.test.ts +0 -35
  159. package/src/stories/Box.stories.tsx +0 -83
  160. package/src/stories/Carousel.stories.tsx +0 -95
  161. package/src/stories/DefinitionList.stories.tsx +0 -51
  162. package/src/stories/Item.stories.tsx +0 -79
  163. package/src/stories/ListItem.stories.tsx +0 -38
  164. package/src/stories/UnorderedList.stories.tsx +0 -73
  165. package/src/styles/tailwind.css +0 -7
  166. package/src/test-setup.ts +0 -1
  167. package/src/utils/author.test.ts +0 -54
  168. package/src/utils/author.tsx +0 -73
  169. package/src/utils/cn.test.ts +0 -48
  170. package/src/utils/cn.ts +0 -14
  171. package/src/utils/is-route-active.test.ts +0 -80
  172. package/src/utils/is-route-active.ts +0 -100
  173. package/src/utils/text.test.ts +0 -152
  174. package/src/utils/text.tsx +0 -209
  175. package/src/vite-env.d.ts +0 -1
  176. /package/dist/{components/background-slideshow.d.ts → background-slideshow/index.d.ts} +0 -0
  177. /package/dist/{components/blurry-gradient.d.ts → blurry-gradient/index.d.ts} +0 -0
  178. /package/dist/{components/countdown.d.ts → countdown/index.d.ts} +0 -0
  179. /package/dist/{components/generic-error.d.ts → generic-error/index.d.ts} +0 -0
  180. /package/dist/hooks/{use-current-route-data.d.ts → use-current-route-data/index.d.ts} +0 -0
  181. /package/dist/hooks/{use-focus-search.d.ts → use-focus-search/index.d.ts} +0 -0
  182. /package/dist/hooks/{use-matches-data.d.ts → use-matches-data/index.d.ts} +0 -0
  183. /package/dist/hooks/{use-media-query.d.ts → use-media-query/index.d.ts} +0 -0
  184. /package/dist/hooks/{use-mobile.d.ts → use-mobile/index.d.ts} +0 -0
  185. /package/dist/hooks/{use-user.d.ts → use-user/index.d.ts} +0 -0
  186. /package/dist/{components/icon-button.d.ts → icon-button/index.d.ts} +0 -0
  187. /package/dist/{components/if.d.ts → if/index.d.ts} +0 -0
  188. /package/dist/{components/iframe.d.ts → iframe/index.d.ts} +0 -0
  189. /package/dist/{components/markdown-container.d.ts → markdown-container/index.d.ts} +0 -0
  190. /package/dist/{components/password-input.d.ts → password-input/index.d.ts} +0 -0
  191. /package/dist/{components/picture.d.ts → picture/index.d.ts} +0 -0
  192. /package/src/hooks/{use-current-route-data.ts → use-current-route-data/use-current-route-data.ts} +0 -0
  193. /package/src/hooks/{use-focus-search.ts → use-focus-search/use-focus-search.ts} +0 -0
  194. /package/src/hooks/{use-matches-data.ts → use-matches-data/use-matches-data.ts} +0 -0
  195. /package/src/hooks/{use-media-query.ts → use-media-query/use-media-query.ts} +0 -0
  196. /package/src/hooks/{use-mobile.ts → use-mobile/use-mobile.ts} +0 -0
  197. /package/src/hooks/{use-nonce.ts → use-nonce/use-nonce.ts} +0 -0
  198. /package/src/hooks/{use-orientation.ts → use-orientation/use-orientation.ts} +0 -0
  199. /package/src/hooks/{use-user.tsx → use-user/use-user.tsx} +0 -0
  200. /package/src/{components → icon-button}/icon-button.tsx +0 -0
  201. /package/src/{components → if}/if.tsx +0 -0
  202. /package/src/{styles/storybook.css → storybook.css} +0 -0
@@ -1,43 +1,36 @@
1
+ import { cn, tv } from '@regardio/tailwind/utils';
1
2
  import type { ElementType, HTMLAttributes } from 'react';
2
- import { cn, cva, type VariantProps } from '../utils/cn';
3
3
  import { lowerCaseSzett, shy } from '../utils/text';
4
4
 
5
- const heading = cva({
5
+ const levelVariants = {
6
+ 1: ['text-2xl'],
7
+ 2: ['text-xl'],
8
+ 3: ['text-lg'],
9
+ 4: ['text-md'],
10
+ 5: ['text-sm'],
11
+ 6: ['text-xs'],
12
+ } as const;
13
+
14
+ const heading = tv({
6
15
  base: [],
7
16
  defaultVariants: {
8
17
  level: 3,
9
18
  },
10
19
  variants: {
11
- level: {
12
- 1: ['text-2xl'],
13
- 2: ['text-xl'],
14
- 3: ['text-lg'],
15
- 4: ['text-md'],
16
- 5: ['text-sm'],
17
- 6: ['text-xs'],
18
- },
19
- variant: {},
20
+ level: levelVariants,
20
21
  },
21
22
  });
22
23
 
23
- export interface HeadingProps
24
- extends HTMLAttributes<HTMLHeadingElement>,
25
- VariantProps<typeof heading> {
24
+ export type HeadingLevel = keyof typeof levelVariants;
25
+
26
+ export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
26
27
  as?: ElementType;
27
28
  className?: string;
29
+ level?: HeadingLevel;
28
30
  locale?: string;
29
31
  }
30
32
 
31
- export type HeadingLevel = HeadingProps['level'];
32
-
33
- export const Heading = ({
34
- as,
35
- children,
36
- className,
37
- level = 2,
38
- variant,
39
- ...props
40
- }: HeadingProps) => {
33
+ export const Heading = ({ as, children, className, level = 2, ...props }: HeadingProps) => {
41
34
  const word = lowerCaseSzett(shy(children));
42
35
  const Component: ElementType = as || `h${level}`;
43
36
 
@@ -46,7 +39,6 @@ export const Heading = ({
46
39
  className={cn(
47
40
  heading({
48
41
  level,
49
- variant,
50
42
  }),
51
43
  className,
52
44
  )}
@@ -0,0 +1,2 @@
1
+ export type { HeadingLevel, HeadingProps } from './heading';
2
+ export { Heading } from './heading';
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Highlight } from '../components/highlight';
2
+ import { Highlight } from './highlight';
3
3
 
4
4
  const meta: Meta<typeof Highlight> = {
5
5
  component: Highlight,
@@ -1,25 +1,29 @@
1
+ import { tv } from '@regardio/tailwind/utils';
1
2
  import type { ComponentProps } from 'react';
2
- import { cva, type VariantProps } from '../utils/cn';
3
3
 
4
- const li = cva({
4
+ const highlightVariants = {
5
+ primary: ['highlight'],
6
+ } as const;
7
+
8
+ const highlight = tv({
5
9
  defaultVariants: {
6
10
  variant: 'primary',
7
11
  },
8
12
  variants: {
9
- variant: {
10
- primary: ['highlight'],
11
- },
13
+ variant: highlightVariants,
12
14
  },
13
15
  });
14
16
 
15
- export interface HighlightProps extends ComponentProps<'mark'>, VariantProps<typeof li> {}
17
+ export type HighlightVariant = keyof typeof highlightVariants;
16
18
 
17
- export const Highlight = (props: HighlightProps) => {
18
- const { children, className, variant } = props;
19
+ export interface HighlightProps extends ComponentProps<'mark'> {
20
+ variant?: HighlightVariant;
21
+ }
19
22
 
23
+ export const Highlight = ({ children, className, variant }: HighlightProps) => {
20
24
  return (
21
25
  <mark
22
- className={li({
26
+ className={highlight({
23
27
  className,
24
28
  variant,
25
29
  })}
@@ -0,0 +1,2 @@
1
+ export type { HighlightProps } from './highlight';
2
+ export { Highlight } from './highlight';
@@ -0,0 +1 @@
1
+ export { useCurrentRouteData } from './use-current-route-data';
@@ -0,0 +1 @@
1
+ export { useFocusSearch } from './use-focus-search';
@@ -0,0 +1 @@
1
+ export { useMatchesData } from './use-matches-data';
@@ -0,0 +1 @@
1
+ export { useMediaQuery } from './use-media-query';
@@ -0,0 +1 @@
1
+ export { useIsMobile } from './use-mobile';
@@ -0,0 +1 @@
1
+ export { NonceProvider, useNonce } from './use-nonce';
@@ -0,0 +1 @@
1
+ export { useOrientation } from './use-orientation';
@@ -0,0 +1,2 @@
1
+ export type { UserContextProviderProps, UserContextType } from './use-user';
2
+ export { UserContext, UserContextProvider, useUser } from './use-user';
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { IconButton } from '../components/icon-button';
2
+ import { IconButton } from './icon-button';
3
3
 
4
4
  const meta: Meta<typeof IconButton> = {
5
5
  component: IconButton,
@@ -0,0 +1,2 @@
1
+ export type { IconButtonProps } from './icon-button';
2
+ export { IconButton } from './icon-button';
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { If } from '../components/if';
2
+ import { If } from './if';
3
3
 
4
4
  const meta: Meta<typeof If> = {
5
5
  component: If,
@@ -0,0 +1 @@
1
+ export { If } from './if';
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Iframe } from '../components/iframe';
2
+ import { Iframe } from './iframe';
3
3
 
4
4
  const meta: Meta<typeof Iframe> = {
5
5
  component: Iframe,
@@ -1,5 +1,5 @@
1
+ import { cn } from '@regardio/tailwind/utils';
1
2
  import type React from 'react';
2
- import { cn } from '../utils/cn';
3
3
 
4
4
  export type IframeProps = {
5
5
  src: string;
@@ -0,0 +1,2 @@
1
+ export type { IframeProps } from './iframe';
2
+ export { Iframe } from './iframe';
@@ -0,0 +1,2 @@
1
+ export type { LinkBaseProps, LinkProps, MarkdownLinkProps, PathResolver } from './link';
2
+ export { Link, LinkBase, MarkdownLink, PathResolverProvider, usePathResolver } from './link';
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { MemoryRouter } from 'react-router';
3
- import { Link } from '../components/link';
3
+ import { Link } from './link';
4
4
 
5
5
  const meta: Meta<typeof Link> = {
6
6
  component: Link,
@@ -1,7 +1,9 @@
1
+ 'use client';
2
+
3
+ import { tv } from '@regardio/tailwind/utils';
1
4
  import { createContext, useCallback, useContext } from 'react';
2
5
  import type { NavLinkProps, NavLinkRenderProps } from 'react-router';
3
6
  import { NavLink } from 'react-router';
4
- import { cva, type VariantProps } from '../utils/cn';
5
7
  import { lowerCaseSzett } from '../utils/text';
6
8
 
7
9
  /**
@@ -120,42 +122,51 @@ export const LinkBase = ({
120
122
  );
121
123
  };
122
124
 
123
- const link = cva({
125
+ const arrowVariants = {
126
+ darr: 'darr',
127
+ larr: 'larr',
128
+ rarr: 'rarr',
129
+ uarr: 'uarr',
130
+ } as const;
131
+
132
+ const linkVariants = {
133
+ button: [
134
+ 'block',
135
+ 'button',
136
+ 'mt-s',
137
+ 'relative',
138
+ 'rarr',
139
+ 'text-right',
140
+ 'text-sm',
141
+ 'tracking-wider',
142
+ 'uppercase',
143
+ ],
144
+ code: ['font-monospace'],
145
+ link: ['rarr', '!bg-transparent', 'uppercase', '!tracking-wider'],
146
+ navtitle: ['block', 'uppercase', 'tracking-wider'],
147
+ primary: [],
148
+ subtitle: ['text-lg'],
149
+ } as const;
150
+
151
+ const link = tv({
124
152
  base: [],
125
153
  defaultVariants: {
126
154
  variant: 'primary',
127
155
  },
128
156
  variants: {
129
- arrow: {
130
- darr: 'darr',
131
- larr: 'larr',
132
- rarr: 'rarr',
133
- uarr: 'uarr',
134
- },
135
- variant: {
136
- button: [
137
- 'block',
138
- 'button',
139
- 'mt-s',
140
- 'relative',
141
- 'rarr',
142
- 'text-right',
143
- 'text-sm',
144
- 'tracking-wider',
145
- 'uppercase',
146
- ],
147
- code: ['font-monospace'],
148
- link: ['rarr', '!bg-transparent', 'uppercase', '!tracking-wider'],
149
- navtitle: ['block', 'uppercase', 'tracking-wider'],
150
- primary: [],
151
- subtitle: ['text-lg'],
152
- },
157
+ arrow: arrowVariants,
158
+ variant: linkVariants,
153
159
  },
154
160
  });
155
161
 
156
- export interface LinkProps extends Omit<NavLinkProps, 'to'>, VariantProps<typeof link> {
162
+ export type LinkArrow = keyof typeof arrowVariants;
163
+ export type LinkVariant = keyof typeof linkVariants;
164
+
165
+ export interface LinkProps extends Omit<NavLinkProps, 'to'> {
166
+ arrow?: LinkArrow;
157
167
  to?: string | Partial<{ pathname?: string; search?: string; hash?: string }>;
158
168
  routeKey?: string;
169
+ variant?: LinkVariant;
159
170
  viewTransition?: boolean;
160
171
  }
161
172
 
@@ -174,7 +185,7 @@ export const Link = ({
174
185
  {...props}
175
186
  className={link({
176
187
  arrow,
177
- className,
188
+ className: typeof className === 'string' ? className : undefined,
178
189
  variant,
179
190
  })}
180
191
  routeKey={routeKey}
@@ -0,0 +1,2 @@
1
+ export { ListItem as Item } from './list-item';
2
+ export { ListRoot as Root } from './list-root';
@@ -0,0 +1,4 @@
1
+ export * as List from './index.parts';
2
+ export type { ListItemProps } from './list-item';
3
+ export type { ListRootProps } from './list-root';
4
+ export type { ListRootContextValue } from './list-root-context';
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type ComponentPropsWithoutRef,
5
+ type ElementType,
6
+ type ForwardedRef,
7
+ forwardRef,
8
+ type ReactNode,
9
+ } from 'react';
10
+ import { useListRootContext } from './list-root-context';
11
+
12
+ type ListItemElement = 'li' | 'dd' | 'dt' | 'div' | 'span';
13
+
14
+ export type ListItemProps<T extends ListItemElement = 'li'> = Omit<
15
+ ComponentPropsWithoutRef<T>,
16
+ 'children'
17
+ > & {
18
+ /**
19
+ * The element type to render.
20
+ * Falls back to the defaultItemElement from ListRoot context, or 'li'.
21
+ */
22
+ render?: T;
23
+ /**
24
+ * The content of the list item.
25
+ */
26
+ children?: ReactNode;
27
+ };
28
+
29
+ function ListItemImpl<T extends ListItemElement = 'li'>(
30
+ props: ListItemProps<T>,
31
+ ref: ForwardedRef<HTMLElement>,
32
+ ) {
33
+ const context = useListRootContext();
34
+
35
+ const { render, children, className, ...elementProps } = props;
36
+
37
+ const resolvedElement = render ?? context?.defaultItemElement ?? 'li';
38
+ const resolvedClassName = context?.defaultItemClassName
39
+ ? className
40
+ ? `${context.defaultItemClassName} ${className}`
41
+ : context.defaultItemClassName
42
+ : className;
43
+
44
+ const Component = resolvedElement as ElementType;
45
+
46
+ return (
47
+ <Component
48
+ className={resolvedClassName}
49
+ ref={ref}
50
+ {...elementProps}
51
+ >
52
+ {children}
53
+ </Component>
54
+ );
55
+ }
56
+
57
+ export const ListItem = forwardRef(ListItemImpl) as <T extends ListItemElement = 'li'>(
58
+ props: ListItemProps<T> & { ref?: ForwardedRef<HTMLElement> },
59
+ ) => ReturnType<typeof ListItemImpl>;
60
+
61
+ export namespace ListItem {
62
+ export type Props<T extends ListItemElement = 'li'> = ListItemProps<T>;
63
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+
5
+ export interface ListRootContextValue {
6
+ /**
7
+ * Default element type for list items.
8
+ * @default 'li'
9
+ */
10
+ defaultItemElement: 'li' | 'dd' | 'dt' | 'div' | 'span';
11
+ /**
12
+ * Default className to apply to all list items.
13
+ */
14
+ defaultItemClassName?: string;
15
+ }
16
+
17
+ export const ListRootContext = createContext<ListRootContextValue | undefined>(undefined);
18
+
19
+ export function useListRootContext(): ListRootContextValue | undefined {
20
+ return useContext(ListRootContext);
21
+ }
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type ComponentPropsWithoutRef,
5
+ type ElementType,
6
+ type ForwardedRef,
7
+ forwardRef,
8
+ type ReactNode,
9
+ useMemo,
10
+ } from 'react';
11
+ import { ListRootContext, type ListRootContextValue } from './list-root-context';
12
+
13
+ type ListRootElement = 'ul' | 'ol' | 'dl' | 'div' | 'menu' | 'nav';
14
+
15
+ export type ListRootProps<T extends ListRootElement = 'ul'> = Omit<
16
+ ComponentPropsWithoutRef<T>,
17
+ 'children'
18
+ > & {
19
+ /**
20
+ * The element type to render.
21
+ * @default 'ul'
22
+ */
23
+ render?: T;
24
+ /**
25
+ * The content of the list.
26
+ */
27
+ children?: ReactNode;
28
+ /**
29
+ * Default element type for list items.
30
+ * When render is 'dl', defaults to 'dd'. Otherwise defaults to 'li'.
31
+ */
32
+ defaultItemElement?: ListRootContextValue['defaultItemElement'];
33
+ /**
34
+ * Default className to apply to all list items.
35
+ */
36
+ defaultItemClassName?: string;
37
+ };
38
+
39
+ function ListRootImpl<T extends ListRootElement = 'ul'>(
40
+ props: ListRootProps<T>,
41
+ ref: ForwardedRef<HTMLElement>,
42
+ ) {
43
+ const {
44
+ render = 'ul' as T,
45
+ children,
46
+ defaultItemElement,
47
+ defaultItemClassName,
48
+ ...elementProps
49
+ } = props;
50
+
51
+ const resolvedDefaultItemElement = defaultItemElement ?? (render === 'dl' ? 'dd' : 'li');
52
+
53
+ const contextValue = useMemo<ListRootContextValue>(
54
+ () => ({
55
+ defaultItemClassName,
56
+ defaultItemElement: resolvedDefaultItemElement,
57
+ }),
58
+ [resolvedDefaultItemElement, defaultItemClassName],
59
+ );
60
+
61
+ const Component = render as ElementType;
62
+
63
+ return (
64
+ <ListRootContext.Provider value={contextValue}>
65
+ <Component
66
+ ref={ref}
67
+ {...elementProps}
68
+ >
69
+ {children}
70
+ </Component>
71
+ </ListRootContext.Provider>
72
+ );
73
+ }
74
+
75
+ export const ListRoot = forwardRef(ListRootImpl) as <T extends ListRootElement = 'ul'>(
76
+ props: ListRootProps<T> & { ref?: ForwardedRef<HTMLElement> },
77
+ ) => ReturnType<typeof ListRootImpl>;
78
+
79
+ export namespace ListRoot {
80
+ export type Props<T extends ListRootElement = 'ul'> = ListRootProps<T>;
81
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * CSS custom properties for List component styling.
3
+ * These can be overridden at any level to customize the appearance.
4
+ *
5
+ * Usage with CSS Modules or plain CSS:
6
+ * .my-list {
7
+ * --list-gap: 1rem;
8
+ * --list-padding: 0;
9
+ * }
10
+ *
11
+ * Usage with Tailwind (via arbitrary properties):
12
+ * <List.Root className="[--list-gap:1rem]">
13
+ */
14
+
15
+ :root {
16
+ /* List Root */
17
+ --list-gap: 0;
18
+ --list-padding: 0;
19
+ --list-margin: 0;
20
+ --list-style: none;
21
+
22
+ /* List Item */
23
+ --list-item-padding: 0;
24
+ --list-item-margin: 0;
25
+
26
+ /* Definition List specific */
27
+ --list-dl-grid-columns: auto 1fr;
28
+ --list-dl-gap-x: 0.5rem;
29
+ --list-dl-gap-y: 0.25rem;
30
+ --list-dt-font-weight: 600;
31
+ --list-dd-margin: 0;
32
+ }
@@ -0,0 +1,119 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { List } from './index';
3
+
4
+ const meta: Meta<typeof List.Root> = {
5
+ component: List.Root,
6
+ parameters: {
7
+ layout: 'padded',
8
+ },
9
+ tags: ['autodocs'],
10
+ title: 'Components/List',
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof List.Root>;
15
+
16
+ export const UnorderedList: Story = {
17
+ render: () => (
18
+ <List.Root>
19
+ <List.Item>First item</List.Item>
20
+ <List.Item>Second item</List.Item>
21
+ <List.Item>Third item</List.Item>
22
+ </List.Root>
23
+ ),
24
+ };
25
+
26
+ export const OrderedList: Story = {
27
+ render: () => (
28
+ <List.Root render="ol">
29
+ <List.Item>Step one</List.Item>
30
+ <List.Item>Step two</List.Item>
31
+ <List.Item>Step three</List.Item>
32
+ </List.Root>
33
+ ),
34
+ };
35
+
36
+ export const DefinitionList: Story = {
37
+ render: () => (
38
+ <List.Root
39
+ className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2"
40
+ render="dl"
41
+ >
42
+ <List.Item
43
+ className="font-semibold"
44
+ render="dt"
45
+ >
46
+ Email
47
+ </List.Item>
48
+ <List.Item>contact@example.com</List.Item>
49
+ <List.Item
50
+ className="font-semibold"
51
+ render="dt"
52
+ >
53
+ Phone
54
+ </List.Item>
55
+ <List.Item>+1 234 567 890</List.Item>
56
+ <List.Item
57
+ className="font-semibold"
58
+ render="dt"
59
+ >
60
+ Address
61
+ </List.Item>
62
+ <List.Item>123 Main Street, City, Country</List.Item>
63
+ </List.Root>
64
+ ),
65
+ };
66
+
67
+ export const WithDefaultItemClassName: Story = {
68
+ render: () => (
69
+ <List.Root defaultItemClassName="py-2 border-b border-gray-200 last:border-0">
70
+ <List.Item>Styled item one</List.Item>
71
+ <List.Item>Styled item two</List.Item>
72
+ <List.Item>Styled item three</List.Item>
73
+ </List.Root>
74
+ ),
75
+ };
76
+
77
+ export const InlineList: Story = {
78
+ render: () => (
79
+ <List.Root className="flex flex-wrap gap-2 list-none p-0">
80
+ <List.Item className="px-2 py-1 bg-gray-100 rounded">Tag 1</List.Item>
81
+ <List.Item className="px-2 py-1 bg-gray-100 rounded">Tag 2</List.Item>
82
+ <List.Item className="px-2 py-1 bg-gray-100 rounded">Tag 3</List.Item>
83
+ </List.Root>
84
+ ),
85
+ };
86
+
87
+ export const NavigationMenu: Story = {
88
+ render: () => (
89
+ <List.Root
90
+ className="flex gap-4"
91
+ defaultItemElement="div"
92
+ render="nav"
93
+ >
94
+ <List.Item className="hover:text-blue-600 cursor-pointer">Home</List.Item>
95
+ <List.Item className="hover:text-blue-600 cursor-pointer">About</List.Item>
96
+ <List.Item className="hover:text-blue-600 cursor-pointer">Contact</List.Item>
97
+ </List.Root>
98
+ ),
99
+ };
100
+
101
+ export const CustomElements: Story = {
102
+ render: () => (
103
+ <List.Root
104
+ className="flex gap-2"
105
+ defaultItemElement="span"
106
+ render="div"
107
+ >
108
+ <List.Item className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
109
+ React
110
+ </List.Item>
111
+ <List.Item className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm">
112
+ TypeScript
113
+ </List.Item>
114
+ <List.Item className="px-3 py-1 bg-purple-100 text-purple-800 rounded-full text-sm">
115
+ Tailwind
116
+ </List.Item>
117
+ </List.Root>
118
+ ),
119
+ };