@saas-ui/react 2.11.2 → 3.0.0-alpha.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 (232) hide show
  1. package/CHANGELOG.md +7 -154
  2. package/dist/index.cjs +8461 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +26 -0
  5. package/dist/index.d.ts +25 -7
  6. package/dist/index.js +8415 -35
  7. package/dist/index.js.map +1 -1
  8. package/package.json +24 -21
  9. package/src/components/accordion.tsx +47 -0
  10. package/src/components/action-bar.tsx +40 -0
  11. package/src/components/alert.tsx +51 -0
  12. package/src/components/app-shell/app-shell.recipe.ts +52 -0
  13. package/src/components/app-shell/app-shell.stories.tsx +51 -0
  14. package/src/components/app-shell/app-shell.tsx +94 -0
  15. package/src/components/app-shell/index.ts +3 -0
  16. package/src/components/avatar.tsx +74 -0
  17. package/src/components/blockquote.tsx +31 -0
  18. package/src/components/breadcrumbs/breadcrumb.stories.tsx +17 -0
  19. package/src/components/breadcrumbs/breadcrumb.tsx +36 -0
  20. package/src/components/breadcrumbs/index.ts +1 -0
  21. package/src/components/breadcrumbs/namespace.ts +8 -0
  22. package/src/components/button/button.recipe.ts +182 -0
  23. package/src/components/button/button.stories.tsx +99 -0
  24. package/src/components/button/button.tsx +55 -0
  25. package/src/components/button/index.ts +2 -0
  26. package/src/components/checkbox/checkbox.tsx +26 -0
  27. package/src/components/checkbox/index.ts +2 -0
  28. package/src/components/checkbox-card.tsx +57 -0
  29. package/src/components/checkbox.tsx +25 -0
  30. package/src/components/clipboard.tsx +107 -0
  31. package/src/components/close-button/close-button.stories.tsx +12 -0
  32. package/src/components/close-button/close-button.tsx +18 -0
  33. package/src/components/close-button/index.ts +2 -0
  34. package/src/components/color-mode.tsx +65 -0
  35. package/src/components/command/command.recipe.ts +17 -0
  36. package/src/components/command/command.stories.tsx +47 -0
  37. package/src/components/command/command.tsx +50 -0
  38. package/src/components/command/index.ts +1 -0
  39. package/src/components/data-list.tsx +37 -0
  40. package/src/components/dialog/dialog.tsx +66 -0
  41. package/src/components/dialog/index.ts +1 -0
  42. package/src/components/dialog/namespace.ts +18 -0
  43. package/src/components/drawer/drawer.tsx +56 -0
  44. package/src/components/drawer/index.ts +3 -0
  45. package/src/components/drawer/namespace.ts +19 -0
  46. package/src/components/empty-state.tsx +34 -0
  47. package/src/components/field.tsx +33 -0
  48. package/src/components/file-button.tsx +166 -0
  49. package/src/components/grid-list/grid-list.recipe.ts +113 -0
  50. package/src/components/hover-card.tsx +35 -0
  51. package/src/components/icon-badge/icon-badge.recipe.ts +57 -0
  52. package/src/components/icon-badge/icon-badge.stories.tsx +38 -0
  53. package/src/components/icon-badge/icon-badge.tsx +59 -0
  54. package/src/components/icon-badge/index.ts +2 -0
  55. package/src/components/icons/create-icon.tsx +41 -0
  56. package/src/components/icons/icons.tsx +121 -0
  57. package/src/components/icons/index.ts +1 -0
  58. package/src/components/input-group/index.ts +1 -0
  59. package/src/components/input-group/input-group.tsx +46 -0
  60. package/src/components/link/index.ts +2 -0
  61. package/src/components/link/link.stories.tsx +17 -0
  62. package/src/components/link/link.test.tsx +33 -0
  63. package/src/components/link/link.tsx +27 -0
  64. package/src/components/link-button.tsx +12 -0
  65. package/src/components/loading-overlay/index.ts +1 -0
  66. package/src/components/loading-overlay/loading-overlay.recipe.ts +61 -0
  67. package/src/components/loading-overlay/loading-overlay.stories.tsx +68 -0
  68. package/src/components/loading-overlay/loading-overlay.tsx +54 -0
  69. package/src/components/loading-overlay/namespace.ts +7 -0
  70. package/src/components/menu.tsx +108 -0
  71. package/src/components/native-select.tsx +57 -0
  72. package/src/components/navbar/index.ts +1 -0
  73. package/src/components/navbar/namespace.ts +9 -0
  74. package/src/components/navbar/navbar.recipe.ts +109 -0
  75. package/src/components/navbar/navbar.stories.tsx +435 -0
  76. package/src/components/navbar/navbar.test.tsx +49 -0
  77. package/src/components/navbar/navbar.tsx +39 -0
  78. package/src/components/number-input/index.ts +2 -0
  79. package/src/components/number-input/number-input.tsx +41 -0
  80. package/src/components/pagination.tsx +207 -0
  81. package/src/components/password-input/index.ts +2 -0
  82. package/src/components/password-input/password-input.tsx +98 -0
  83. package/src/components/persona/index.ts +2 -0
  84. package/src/components/persona/namespace.ts +18 -0
  85. package/src/components/persona/persona-primitive.tsx +220 -0
  86. package/src/components/persona/persona.recipe.ts +94 -0
  87. package/src/components/persona/persona.stories.tsx +101 -0
  88. package/src/components/persona/persona.tsx +143 -0
  89. package/src/components/pin-input/index.ts +2 -0
  90. package/src/components/pin-input/pin-input.tsx +36 -0
  91. package/src/components/popover.tsx +58 -0
  92. package/src/components/progress-circle.tsx +37 -0
  93. package/src/components/progress.tsx +40 -0
  94. package/src/components/prose.tsx +264 -0
  95. package/src/components/provider.tsx +12 -0
  96. package/src/components/radio/index.ts +2 -0
  97. package/src/components/radio/radio.tsx +27 -0
  98. package/src/components/radio-card.tsx +57 -0
  99. package/src/components/radio.tsx +24 -0
  100. package/src/components/rating.tsx +27 -0
  101. package/src/components/search-input/index.ts +2 -0
  102. package/src/components/search-input/search-input.stories.tsx +63 -0
  103. package/src/components/search-input/search-input.tsx +134 -0
  104. package/src/components/segmented-control.tsx +47 -0
  105. package/src/components/select/index.ts +1 -0
  106. package/src/components/select/namespace.ts +18 -0
  107. package/src/components/select/select.tsx +135 -0
  108. package/src/components/sidebar/index.ts +7 -0
  109. package/src/components/sidebar/namespace.ts +27 -0
  110. package/src/components/sidebar/sidebar-item.recipe.ts +65 -0
  111. package/src/components/sidebar/sidebar.recipe.ts +237 -0
  112. package/src/components/sidebar/sidebar.stories.tsx +903 -0
  113. package/src/components/sidebar/sidebar.tsx +204 -0
  114. package/src/components/skeleton.tsx +44 -0
  115. package/src/components/slider.tsx +53 -0
  116. package/src/components/spinner/index.ts +2 -0
  117. package/src/components/spinner/spinner.stories.tsx +19 -0
  118. package/src/components/spinner/spinner.tsx +21 -0
  119. package/src/components/stat.tsx +75 -0
  120. package/src/components/status.tsx +29 -0
  121. package/src/components/stepper-input.tsx +49 -0
  122. package/src/components/steps/index.ts +1 -0
  123. package/src/components/steps/namespace.ts +16 -0
  124. package/src/components/steps/steps.tsx +82 -0
  125. package/src/components/switch/index.ts +3 -0
  126. package/src/components/switch/switch.tsx +39 -0
  127. package/src/components/tag.tsx +39 -0
  128. package/src/components/timeline.tsx +17 -0
  129. package/src/components/toaster.tsx +43 -0
  130. package/src/components/toggle-tip.tsx +62 -0
  131. package/src/components/tooltip.tsx +46 -0
  132. package/src/index.ts +6 -7
  133. package/src/preset.ts +9 -0
  134. package/src/provider/index.ts +4 -0
  135. package/src/provider/sui-provider.tsx +34 -0
  136. package/src/provider/use-link.test.tsx +60 -0
  137. package/src/provider/use-link.tsx +13 -0
  138. package/src/theme/animation-styles.ts +53 -0
  139. package/src/theme/breakpoints.ts +11 -0
  140. package/src/theme/conditions.ts +26 -0
  141. package/src/theme/fluid-font-sizes.ts +65 -0
  142. package/src/theme/global-css.ts +94 -0
  143. package/src/theme/index.ts +72 -0
  144. package/src/theme/layer-styles.ts +116 -0
  145. package/src/theme/recipes/chakra/accordion.ts +145 -0
  146. package/src/theme/recipes/chakra/action-bar.ts +62 -0
  147. package/src/theme/recipes/chakra/alert.ts +157 -0
  148. package/src/theme/recipes/chakra/avatar.ts +141 -0
  149. package/src/theme/recipes/chakra/badge.ts +67 -0
  150. package/src/theme/recipes/chakra/blockquote.ts +83 -0
  151. package/src/theme/recipes/chakra/breadcrumb.ts +94 -0
  152. package/src/theme/recipes/chakra/card.ts +99 -0
  153. package/src/theme/recipes/chakra/checkbox-card.ts +212 -0
  154. package/src/theme/recipes/chakra/checkbox.ts +70 -0
  155. package/src/theme/recipes/chakra/checkmark.ts +83 -0
  156. package/src/theme/recipes/chakra/code.ts +17 -0
  157. package/src/theme/recipes/chakra/collapsible.ts +20 -0
  158. package/src/theme/recipes/chakra/container.ts +26 -0
  159. package/src/theme/recipes/chakra/data-list.ts +80 -0
  160. package/src/theme/recipes/chakra/dialog.ts +225 -0
  161. package/src/theme/recipes/chakra/drawer.ts +201 -0
  162. package/src/theme/recipes/chakra/editable.ts +88 -0
  163. package/src/theme/recipes/chakra/empty-state.ts +88 -0
  164. package/src/theme/recipes/chakra/field.ts +68 -0
  165. package/src/theme/recipes/chakra/fieldset.ts +62 -0
  166. package/src/theme/recipes/chakra/file-upload.ts +96 -0
  167. package/src/theme/recipes/chakra/heading.ts +27 -0
  168. package/src/theme/recipes/chakra/hover-card.ts +68 -0
  169. package/src/theme/recipes/chakra/icon.ts +30 -0
  170. package/src/theme/recipes/chakra/input-addon.ts +40 -0
  171. package/src/theme/recipes/chakra/input.ts +96 -0
  172. package/src/theme/recipes/chakra/kbd.ts +60 -0
  173. package/src/theme/recipes/chakra/link.ts +37 -0
  174. package/src/theme/recipes/chakra/list.ts +67 -0
  175. package/src/theme/recipes/chakra/mark.ts +27 -0
  176. package/src/theme/recipes/chakra/menu.ts +124 -0
  177. package/src/theme/recipes/chakra/native-select.ts +140 -0
  178. package/src/theme/recipes/chakra/number-input.ts +115 -0
  179. package/src/theme/recipes/chakra/pin-input.ts +27 -0
  180. package/src/theme/recipes/chakra/popover.ts +86 -0
  181. package/src/theme/recipes/chakra/progress-circle.ts +94 -0
  182. package/src/theme/recipes/chakra/progress.ts +127 -0
  183. package/src/theme/recipes/chakra/radio-card.ts +220 -0
  184. package/src/theme/recipes/chakra/radio-group.ts +72 -0
  185. package/src/theme/recipes/chakra/radiomark.ts +107 -0
  186. package/src/theme/recipes/chakra/rating-group.ts +94 -0
  187. package/src/theme/recipes/chakra/segment-group.ts +117 -0
  188. package/src/theme/recipes/chakra/select.ts +282 -0
  189. package/src/theme/recipes/chakra/separator.ts +51 -0
  190. package/src/theme/recipes/chakra/skeleton.ts +53 -0
  191. package/src/theme/recipes/chakra/skip-nav-link.ts +34 -0
  192. package/src/theme/recipes/chakra/slider.ts +178 -0
  193. package/src/theme/recipes/chakra/spinner.ts +32 -0
  194. package/src/theme/recipes/chakra/stat.ts +79 -0
  195. package/src/theme/recipes/chakra/status.ts +48 -0
  196. package/src/theme/recipes/chakra/steps.ts +218 -0
  197. package/src/theme/recipes/chakra/switch.ts +167 -0
  198. package/src/theme/recipes/chakra/table.ts +172 -0
  199. package/src/theme/recipes/chakra/tabs.ts +280 -0
  200. package/src/theme/recipes/chakra/tag.ts +131 -0
  201. package/src/theme/recipes/chakra/textarea.ts +88 -0
  202. package/src/theme/recipes/chakra/timeline.ts +138 -0
  203. package/src/theme/recipes/chakra/toast.ts +96 -0
  204. package/src/theme/recipes/chakra/tooltip.ts +40 -0
  205. package/src/theme/recipes.ts +46 -0
  206. package/src/theme/semantic-tokens/colors.ts +403 -0
  207. package/src/theme/semantic-tokens/radii.ts +7 -0
  208. package/src/theme/semantic-tokens/shadows.ts +52 -0
  209. package/src/theme/slot-recipes.ts +104 -0
  210. package/src/theme/text-styles.ts +39 -0
  211. package/src/theme/tokens/animations.ts +8 -0
  212. package/src/theme/tokens/aspect-ratios.ts +10 -0
  213. package/src/theme/tokens/blurs.ts +12 -0
  214. package/src/theme/tokens/borders.ts +9 -0
  215. package/src/theme/tokens/colors.ts +177 -0
  216. package/src/theme/tokens/cursor.ts +12 -0
  217. package/src/theme/tokens/durations.ts +11 -0
  218. package/src/theme/tokens/easings.ts +10 -0
  219. package/src/theme/tokens/font-sizes.ts +20 -0
  220. package/src/theme/tokens/font-weights.ts +13 -0
  221. package/src/theme/tokens/fonts.ts +15 -0
  222. package/src/theme/tokens/keyframes.ts +173 -0
  223. package/src/theme/tokens/letter-spacing.ts +9 -0
  224. package/src/theme/tokens/line-heights.ts +19 -0
  225. package/src/theme/tokens/radius.ts +18 -0
  226. package/src/theme/tokens/sizes.ts +71 -0
  227. package/src/theme/tokens/spacing.ts +38 -0
  228. package/src/theme/tokens/z-indices.ts +34 -0
  229. package/src/theme/utils.ts +46 -0
  230. package/dist/index.d.mts +0 -8
  231. package/dist/index.mjs +0 -11
  232. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,435 @@
1
+ import React, { type HTMLProps, forwardRef } from 'react'
2
+
3
+ import {
4
+ Box,
5
+ Button,
6
+ Container,
7
+ Drawer,
8
+ HStack,
9
+ Menu,
10
+ Skeleton as SkeletonPrimitive,
11
+ type SkeletonProps,
12
+ Stack,
13
+ useDisclosure,
14
+ } from '@chakra-ui/react'
15
+ import { SaasUIIcon } from '@saas-ui/assets'
16
+ import { Meta, StoryObj } from '@storybook/react'
17
+ import { LuMenu as FiMenu, LuX as FiX, LuX } from 'react-icons/lu'
18
+
19
+ import { AppShell } from '../app-shell'
20
+ import { Persona } from '../persona'
21
+ import { SearchInput } from '../search-input'
22
+ import { Navbar } from './index.ts'
23
+
24
+ export default {
25
+ title: 'Components/Navbar',
26
+ parameters: {
27
+ layout: 'fullscreen',
28
+ },
29
+ argTypes: {
30
+ position: {
31
+ control: {
32
+ type: 'select',
33
+ },
34
+ options: ['static', 'fixed'],
35
+ },
36
+ maxWidth: {
37
+ control: {
38
+ type: 'select',
39
+ },
40
+ options: ['sm', 'md', 'lg', 'xl', '2xl', 'full'],
41
+ },
42
+ },
43
+ } as Meta
44
+
45
+ type Story = StoryObj<typeof Navbar.Root>
46
+
47
+ const AppLogo = () => <SaasUIIcon width="28px" height="28px" />
48
+
49
+ const Link = forwardRef<HTMLAnchorElement, HTMLProps<HTMLAnchorElement>>(
50
+ ({ children, ...props }, ref) => {
51
+ return (
52
+ <Link ref={ref} onClick={(e) => e.preventDefault()} {...props}>
53
+ {children}
54
+ </Link>
55
+ )
56
+ },
57
+ )
58
+
59
+ export interface SkeletonTextProps extends SkeletonProps {
60
+ noOfLines?: number
61
+ }
62
+
63
+ const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
64
+ function Skeleton(props, ref) {
65
+ return (
66
+ <SkeletonPrimitive ref={ref} variant="none" bg="bg.subtle" {...props} />
67
+ )
68
+ },
69
+ )
70
+
71
+ const SkeletonText = forwardRef<HTMLDivElement, SkeletonTextProps>(
72
+ function SkeletonText(props, ref) {
73
+ const { noOfLines = 3, gap, ...rest } = props
74
+ return (
75
+ <Stack gap={gap} width="full" ref={ref}>
76
+ {Array.from({ length: noOfLines }).map((_, index) => (
77
+ <SkeletonPrimitive
78
+ height="4"
79
+ key={index}
80
+ _last={{ maxW: '80%' }}
81
+ variant="none"
82
+ bg="bg.subtle"
83
+ {...rest}
84
+ />
85
+ ))}
86
+ </Stack>
87
+ )
88
+ },
89
+ )
90
+
91
+ const App = React.forwardRef(({ children, navbar }: any, ref: any) => {
92
+ return (
93
+ <AppShell header={navbar}>
94
+ <Box ref={ref} height="100%" width="100%" overflowY="auto">
95
+ {children}
96
+ <Container
97
+ maxW="container.xl"
98
+ pt="8"
99
+ px="8"
100
+ display="flex"
101
+ flexDirection="column"
102
+ margin="0 auto"
103
+ >
104
+ <Stack gap="4" mb="14">
105
+ <Skeleton width="100px" height="24px" />
106
+ </Stack>
107
+ <Stack direction="row" gap="8" mb="14">
108
+ <Stack gap="4" flex="1">
109
+ <Skeleton width="100px" height="20px" />
110
+ <SkeletonText />
111
+ </Stack>
112
+ <Stack gap="4" flex="1">
113
+ <Skeleton width="100px" height="20px" />
114
+ <SkeletonText />
115
+ </Stack>
116
+ </Stack>
117
+ <Stack direction="row" gap="8">
118
+ <Stack gap="4" flex="1">
119
+ <Skeleton width="100px" height="20px" />
120
+ <SkeletonText />
121
+ </Stack>
122
+ <Stack gap="4" flex="1">
123
+ <Skeleton width="100px" height="20px" />
124
+ <SkeletonText />
125
+ </Stack>
126
+ </Stack>
127
+ </Container>
128
+ </Box>
129
+ </AppShell>
130
+ )
131
+ })
132
+
133
+ App.displayName = 'App'
134
+
135
+ const Template = (args: Navbar.RootProps) => {
136
+ const parentRef = React.useRef(null)
137
+
138
+ return (
139
+ <App ref={parentRef}>
140
+ <Navbar.Root {...args} parentRef={parentRef}>
141
+ <Navbar.Content>
142
+ <Navbar.Brand>
143
+ <AppLogo />
144
+ </Navbar.Brand>
145
+ <HStack display={{ base: 'hidden', md: 'flex' }}>
146
+ <Navbar.Item asChild>
147
+ <Link href="#">Features</Link>
148
+ </Navbar.Item>
149
+ <Navbar.Item active asChild>
150
+ <Link href="#">Customers</Link>
151
+ </Navbar.Item>
152
+ <Navbar.Item asChild>
153
+ <Link href="#">Integrations</Link>
154
+ </Navbar.Item>
155
+ <Navbar.Item asChild>
156
+ <Link href="#">Pricing</Link>
157
+ </Navbar.Item>
158
+ </HStack>
159
+ <HStack justifyContent="end" gap="2">
160
+ <Navbar.Item asChild>
161
+ <Link href="#">Login</Link>
162
+ </Navbar.Item>
163
+ <Button variant="solid" asChild>
164
+ <Link href="#">Sign Up</Link>
165
+ </Button>
166
+ </HStack>
167
+ </Navbar.Content>
168
+ </Navbar.Root>
169
+ </App>
170
+ )
171
+ }
172
+
173
+ const WithMenuTemplate = (args: Navbar.RootProps) => {
174
+ const parentRef = React.useRef(null)
175
+ const mobileNav = useDisclosure()
176
+ const menuItems = ['Features', 'Customers', 'Integrations', 'Pricing']
177
+
178
+ return (
179
+ <App ref={parentRef}>
180
+ <Navbar.Root parentRef={parentRef} position="sticky" {...args}>
181
+ <Navbar.Content>
182
+ <Navbar.Brand>
183
+ <AppLogo />
184
+ </Navbar.Brand>
185
+
186
+ <HStack display={{ base: 'none', sm: 'flex' }}>
187
+ <Navbar.Item asChild>
188
+ <Link href="#">Features</Link>
189
+ </Navbar.Item>
190
+ <Navbar.Item active asChild>
191
+ <Link href="#">Customers</Link>
192
+ </Navbar.Item>
193
+ <Navbar.Item asChild>
194
+ <Link href="#">Integrations</Link>
195
+ </Navbar.Item>
196
+ <Navbar.Item asChild>
197
+ <Link href="#">Pricing</Link>
198
+ </Navbar.Item>
199
+ </HStack>
200
+
201
+ <HStack justifyContent="end" gap="2">
202
+ <Navbar.Item asChild>
203
+ <Link href="#">Login</Link>
204
+ </Navbar.Item>
205
+
206
+ <Button variant="solid" asChild>
207
+ <Link href="#">Sign Up</Link>
208
+ </Button>
209
+
210
+ <Button
211
+ aria-label={mobileNav.open ? 'Close menu' : 'Open menu'}
212
+ display={{ base: 'inline-flex', sm: 'none' }}
213
+ onClick={mobileNav.onToggle}
214
+ variant="ghost"
215
+ >
216
+ {mobileNav.open ? <FiX /> : <FiMenu />}
217
+ </Button>
218
+ </HStack>
219
+ </Navbar.Content>
220
+
221
+ <Drawer.Root open={mobileNav.open} onOpenChange={mobileNav.onToggle}>
222
+ <Drawer.Backdrop />
223
+ <Drawer.Content>
224
+ <Drawer.Header>
225
+ <Drawer.CloseTrigger>
226
+ <Button variant="ghost">
227
+ <LuX />
228
+ </Button>
229
+ </Drawer.CloseTrigger>
230
+ </Drawer.Header>
231
+ <Drawer.Body fontSize="md">
232
+ <Stack direction="column" gap="4">
233
+ {menuItems.map((item, index) => (
234
+ <Navbar.Item key={`${item}-${index}`} width="full" asChild>
235
+ <Link href="#">{item}</Link>
236
+ </Navbar.Item>
237
+ ))}
238
+ </Stack>
239
+ </Drawer.Body>
240
+ </Drawer.Content>
241
+ </Drawer.Root>
242
+ </Navbar.Root>
243
+ </App>
244
+ )
245
+ }
246
+
247
+ const WithUserMenuTemplate = (args: Navbar.RootProps) => {
248
+ return (
249
+ <App>
250
+ <Navbar.Root {...args}>
251
+ <Navbar.Content>
252
+ <Navbar.Brand>
253
+ <AppLogo />
254
+ </Navbar.Brand>
255
+
256
+ <HStack display={{ base: 'hidden', sm: 'flex' }}>
257
+ <Navbar.Item asChild active>
258
+ <Link href="#">Inbox</Link>
259
+ </Navbar.Item>
260
+ <Navbar.Item asChild>
261
+ <Link href="#">Contacts</Link>
262
+ </Navbar.Item>
263
+ <Navbar.Item asChild>
264
+ <Link href="#">Tasks</Link>
265
+ </Navbar.Item>
266
+ </HStack>
267
+
268
+ <Box>
269
+ <Menu.Root>
270
+ <Menu.Trigger>
271
+ <Persona.Avatar
272
+ src="/showcase-avatar.jpg"
273
+ name="Beatriz"
274
+ size="xs"
275
+ >
276
+ <Persona.PresenceBadge presence="online" />
277
+ </Persona.Avatar>
278
+ </Menu.Trigger>
279
+ <Menu.Content>
280
+ <Menu.ItemGroup title="beatriz@saas-ui.dev">
281
+ <Menu.Item value="profile" asChild>
282
+ <Link href="#">Profile</Link>
283
+ </Menu.Item>
284
+ <Menu.Item value="settings" asChild>
285
+ <Link href="#">Settings</Link>
286
+ </Menu.Item>
287
+ <Menu.Item value="help" asChild>
288
+ <Link href="#">Help & feedback</Link>
289
+ </Menu.Item>
290
+ </Menu.ItemGroup>
291
+ <Menu.Separator />
292
+ <Menu.Item value="logout" asChild>
293
+ <Link href="#">Log out</Link>
294
+ </Menu.Item>
295
+ </Menu.Content>
296
+ </Menu.Root>
297
+ </Box>
298
+ </Navbar.Content>
299
+ </Navbar.Root>
300
+ </App>
301
+ )
302
+ }
303
+
304
+ const WithSearchInputTemplate = (args: Navbar.RootProps) => {
305
+ return (
306
+ <App>
307
+ <Navbar.Root {...args}>
308
+ <Navbar.Content>
309
+ <Navbar.Brand>
310
+ <AppLogo />
311
+ </Navbar.Brand>
312
+
313
+ <HStack display={{ base: 'hidden', sm: 'flex' }} gap="1">
314
+ <Navbar.Item asChild>
315
+ <Link href="#">Inbox</Link>
316
+ </Navbar.Item>
317
+ <Navbar.Item active asChild>
318
+ <Link href="#">Contacts</Link>
319
+ </Navbar.Item>
320
+ <Navbar.Item asChild>
321
+ <Link href="#">Tasks</Link>
322
+ </Navbar.Item>
323
+ </HStack>
324
+
325
+ <HStack gap="4">
326
+ <Box width="180px">
327
+ <SearchInput size="sm" />
328
+ </Box>
329
+ <Menu.Root>
330
+ <Menu.Trigger>
331
+ <Persona.Avatar
332
+ src="/showcase-avatar.jpg"
333
+ name="Beatriz"
334
+ size="xs"
335
+ >
336
+ <Persona.PresenceBadge presence="online" />
337
+ </Persona.Avatar>
338
+ </Menu.Trigger>
339
+ <Menu.Content>
340
+ <Menu.ItemGroup title="beatriz@saas-ui.dev">
341
+ <Menu.Item value="profile" asChild>
342
+ <Link href="#">Profile</Link>
343
+ </Menu.Item>
344
+ <Menu.Item value="settings" asChild>
345
+ <Link href="#">Settings</Link>
346
+ </Menu.Item>
347
+ <Menu.Item value="help" asChild>
348
+ <Link href="#">Help & feedback</Link>
349
+ </Menu.Item>
350
+ </Menu.ItemGroup>
351
+ <Menu.Separator />
352
+ <Menu.Item value="logout" asChild>
353
+ <Link href="#">Log out</Link>
354
+ </Menu.Item>
355
+ </Menu.Content>
356
+ </Menu.Root>
357
+ </HStack>
358
+ </Navbar.Content>
359
+ </Navbar.Root>
360
+ </App>
361
+ )
362
+ }
363
+
364
+ export const Static: Story = {
365
+ render: Template,
366
+
367
+ args: {
368
+ position: 'static',
369
+ },
370
+ }
371
+
372
+ export const Sticky: Story = {
373
+ render: Template,
374
+
375
+ args: {
376
+ position: 'sticky',
377
+ },
378
+ }
379
+
380
+ export const Border: Story = {
381
+ render: Template,
382
+
383
+ args: {
384
+ position: 'sticky',
385
+ borderBottomWidth: '1px',
386
+ },
387
+ }
388
+
389
+ export const BlurredBg: Story = {
390
+ render: Template,
391
+
392
+ args: {
393
+ position: 'sticky',
394
+ borderBottomWidth: '1px',
395
+ background: 'transparent',
396
+ backdropFilter: 'blur(4px)',
397
+ },
398
+ }
399
+
400
+ export const Shadow: Story = {
401
+ render: Template,
402
+
403
+ args: {
404
+ position: 'sticky',
405
+ background: 'transparent',
406
+ backdropFilter: 'blur(4px)',
407
+ css: {
408
+ '&:not([data-at-top])': {
409
+ borderBottomWidth: '1px',
410
+ boxShadow: 'lg',
411
+ },
412
+ },
413
+ },
414
+ }
415
+
416
+ export const HideOnScroll: Story = {
417
+ render: Template,
418
+
419
+ args: {
420
+ position: 'sticky',
421
+ shouldHideOnScroll: true,
422
+ },
423
+ }
424
+
425
+ export const WithMenu: Story = {
426
+ render: WithMenuTemplate,
427
+ }
428
+
429
+ export const WithUserMenu: Story = {
430
+ render: WithUserMenuTemplate,
431
+ }
432
+
433
+ export const WithSearchInput: Story = {
434
+ render: WithSearchInputTemplate,
435
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react'
2
+ import { render, testStories } from '@saas-ui/test-utils'
3
+
4
+ import { Navbar, NavbarBrand, NavbarItem, NavbarContent } from './index'
5
+
6
+ import * as stories from './navbar.stories'
7
+
8
+ testStories<typeof stories>(stories)
9
+
10
+ describe('Navbar', () => {
11
+ it('should render correctly', () => {
12
+ const wrapper = render(<Navbar />)
13
+
14
+ expect(() => wrapper.unmount()).not.toThrow()
15
+ })
16
+
17
+ it('ref should be forwarded', () => {
18
+ const ref = React.createRef<HTMLDivElement>()
19
+
20
+ render(<Navbar ref={ref} />)
21
+ expect(ref.current).not.toBeNull()
22
+ })
23
+
24
+ it('should render correctly with brand', () => {
25
+ const wrapper = render(
26
+ <Navbar>
27
+ <NavbarBrand data-testid="navbar-test">Saas UI</NavbarBrand>
28
+ </Navbar>
29
+ )
30
+
31
+ expect(wrapper.getByTestId('navbar-test')).toBeInTheDocument()
32
+ })
33
+
34
+ it('should render correctly content children', () => {
35
+ const wrapper = render(
36
+ <Navbar>
37
+ <NavbarContent data-testid="navbar-content-test">
38
+ <NavbarItem>Dashboard</NavbarItem>
39
+ <NavbarItem>Contacts</NavbarItem>
40
+ <NavbarItem>Settings</NavbarItem>
41
+ </NavbarContent>
42
+ </Navbar>
43
+ )
44
+
45
+ const navbarContent = wrapper.getByTestId('navbar-content-test')
46
+
47
+ expect(navbarContent.children.length).toBe(3)
48
+ })
49
+ })
@@ -0,0 +1,39 @@
1
+ import { type HTMLChakraProps, createSlotRecipeContext } from '@chakra-ui/react'
2
+ import { Navbar } from '@saas-ui/core/navbar'
3
+
4
+ const {
5
+ withProvider,
6
+ withContext,
7
+ useStyles: useNavbarStyles,
8
+ } = createSlotRecipeContext({
9
+ key: 'navbar',
10
+ })
11
+
12
+ export { useNavbarStyles }
13
+
14
+ export type NavbarRootProps = Navbar.RootProps & HTMLChakraProps<'div'>
15
+
16
+ export const NavbarRoot = withProvider<HTMLDivElement, NavbarRootProps>(
17
+ Navbar.Root,
18
+ 'root',
19
+ )
20
+
21
+ export const NavbarBrand = withContext<HTMLDivElement, HTMLChakraProps<'div'>>(
22
+ Navbar.Brand,
23
+ 'brand',
24
+ )
25
+
26
+ export const NavbarContent = withContext<
27
+ HTMLDivElement,
28
+ HTMLChakraProps<'div'>
29
+ >(Navbar.Content, 'content')
30
+
31
+ export const NavbarItem = withContext<HTMLLIElement, HTMLChakraProps<'li'>>(
32
+ Navbar.Item,
33
+ 'item',
34
+ )
35
+
36
+ export const NavbarLink = withContext<HTMLAnchorElement, HTMLChakraProps<'a'>>(
37
+ Navbar.Link,
38
+ 'link',
39
+ )
@@ -0,0 +1,2 @@
1
+ export { NumberInput } from './number-input.tsx'
2
+ export type { NumberInputProps } from './number-input.tsx'
@@ -0,0 +1,41 @@
1
+ import { forwardRef } from 'react'
2
+
3
+ import { NumberInput as ChakraNumberInput } from '@chakra-ui/react'
4
+
5
+ import { InputGroup } from '#components/input-group/index.ts'
6
+
7
+ export interface NumberInputProps
8
+ extends Omit<ChakraNumberInput.RootProps, 'children'> {
9
+ rootRef?: React.Ref<HTMLDivElement>
10
+ hideControls?: boolean
11
+ startElement?: React.ReactNode
12
+ endElement?: React.ReactNode
13
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>
14
+ }
15
+
16
+ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
17
+ function NumberInput(props, ref) {
18
+ const {
19
+ hideControls,
20
+ startElement,
21
+ endElement,
22
+ inputProps,
23
+ rootRef,
24
+ ...rest
25
+ } = props
26
+ return (
27
+ <ChakraNumberInput.Root ref={rootRef} {...rest}>
28
+ <InputGroup startElement={startElement} endElement={endElement}>
29
+ <ChakraNumberInput.Input ref={ref} {...inputProps} />
30
+ </InputGroup>
31
+
32
+ {!hideControls && !endElement ? (
33
+ <ChakraNumberInput.Control>
34
+ <ChakraNumberInput.IncrementTrigger />
35
+ <ChakraNumberInput.DecrementTrigger />
36
+ </ChakraNumberInput.Control>
37
+ ) : null}
38
+ </ChakraNumberInput.Root>
39
+ )
40
+ },
41
+ )