@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,27 @@
1
+ import { forwardRef } from 'react'
2
+
3
+ import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react'
4
+
5
+ export interface RadioProps extends ChakraRadioGroup.ItemProps {
6
+ rootRef?: React.Ref<HTMLDivElement>
7
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>
8
+ }
9
+
10
+ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
11
+ function Radio(props, ref) {
12
+ const { children, inputProps, rootRef, ...rest } = props
13
+ return (
14
+ <ChakraRadioGroup.Item ref={rootRef} {...rest}>
15
+ <ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
16
+ <ChakraRadioGroup.ItemIndicator />
17
+ {children && (
18
+ <ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
19
+ )}
20
+ </ChakraRadioGroup.Item>
21
+ )
22
+ },
23
+ )
24
+
25
+ export type RadioGroupProps = ChakraRadioGroup.RootProps
26
+
27
+ export const RadioGroup = ChakraRadioGroup.Root
@@ -0,0 +1,57 @@
1
+ import { RadioCard } from "@chakra-ui/react"
2
+ import { Fragment, forwardRef } from "react"
3
+
4
+ interface RadioCardItemProps extends RadioCard.ItemProps {
5
+ icon?: React.ReactElement
6
+ label?: React.ReactNode
7
+ description?: React.ReactNode
8
+ addon?: React.ReactNode
9
+ indicator?: React.ReactNode | null
10
+ indicatorPlacement?: "start" | "end" | "inside"
11
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>
12
+ }
13
+
14
+ export const RadioCardItem = forwardRef<HTMLInputElement, RadioCardItemProps>(
15
+ function RadioCardItem(props, ref) {
16
+ const {
17
+ inputProps,
18
+ label,
19
+ description,
20
+ addon,
21
+ icon,
22
+ indicator = <RadioCard.ItemIndicator />,
23
+ indicatorPlacement = "end",
24
+ ...rest
25
+ } = props
26
+
27
+ const hasContent = label || description || icon
28
+ const ContentWrapper = indicator ? RadioCard.ItemContent : Fragment
29
+
30
+ return (
31
+ <RadioCard.Item {...rest}>
32
+ <RadioCard.ItemHiddenInput ref={ref} {...inputProps} />
33
+ <RadioCard.ItemControl>
34
+ {indicatorPlacement === "start" && indicator}
35
+ {hasContent && (
36
+ <ContentWrapper>
37
+ {icon}
38
+ {label && <RadioCard.ItemText>{label}</RadioCard.ItemText>}
39
+ {description && (
40
+ <RadioCard.ItemDescription>
41
+ {description}
42
+ </RadioCard.ItemDescription>
43
+ )}
44
+ {indicatorPlacement === "inside" && indicator}
45
+ </ContentWrapper>
46
+ )}
47
+ {indicatorPlacement === "end" && indicator}
48
+ </RadioCard.ItemControl>
49
+ {addon && <RadioCard.ItemAddon>{addon}</RadioCard.ItemAddon>}
50
+ </RadioCard.Item>
51
+ )
52
+ },
53
+ )
54
+
55
+ export const RadioCardRoot = RadioCard.Root
56
+ export const RadioCardLabel = RadioCard.Label
57
+ export const RadioCardItemIndicator = RadioCard.ItemIndicator
@@ -0,0 +1,24 @@
1
+ import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
2
+ import { forwardRef } from "react"
3
+
4
+ export interface RadioProps extends ChakraRadioGroup.ItemProps {
5
+ rootRef?: React.Ref<HTMLDivElement>
6
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>
7
+ }
8
+
9
+ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
10
+ function Radio(props, ref) {
11
+ const { children, inputProps, rootRef, ...rest } = props
12
+ return (
13
+ <ChakraRadioGroup.Item ref={rootRef} {...rest}>
14
+ <ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
15
+ <ChakraRadioGroup.ItemIndicator />
16
+ {children && (
17
+ <ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
18
+ )}
19
+ </ChakraRadioGroup.Item>
20
+ )
21
+ },
22
+ )
23
+
24
+ export const RadioGroup = ChakraRadioGroup.Root
@@ -0,0 +1,27 @@
1
+ import { RatingGroup } from "@chakra-ui/react"
2
+ import { forwardRef } from "react"
3
+
4
+ export interface RatingProps extends RatingGroup.RootProps {
5
+ icon?: React.ReactElement
6
+ count?: number
7
+ label?: React.ReactNode
8
+ }
9
+
10
+ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
11
+ function Rating(props, ref) {
12
+ const { icon, count = 5, label, ...rest } = props
13
+ return (
14
+ <RatingGroup.Root ref={ref} count={count} {...rest}>
15
+ {label && <RatingGroup.Label>{label}</RatingGroup.Label>}
16
+ <RatingGroup.HiddenInput />
17
+ <RatingGroup.Control>
18
+ {Array.from({ length: count }).map((_, index) => (
19
+ <RatingGroup.Item key={index} index={index + 1}>
20
+ <RatingGroup.ItemIndicator icon={icon} />
21
+ </RatingGroup.Item>
22
+ ))}
23
+ </RatingGroup.Control>
24
+ </RatingGroup.Root>
25
+ )
26
+ },
27
+ )
@@ -0,0 +1,2 @@
1
+ export { SearchInput } from './search-input.tsx'
2
+ export type { SearchInputProps } from './search-input.tsx'
@@ -0,0 +1,63 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { Container, Stack } from '@chakra-ui/react'
4
+ import { StoryObj } from '@storybook/react'
5
+ import { RiCloseLine, RiSearch2Line } from 'react-icons/ri'
6
+
7
+ import { SearchInput, SearchInputProps } from './search-input'
8
+
9
+ export default {
10
+ title: 'Components/SearchInput',
11
+ component: SearchInput,
12
+ decorators: [
13
+ (Story: any) => (
14
+ <Container mt="40px">
15
+ <Story />
16
+ </Container>
17
+ ),
18
+ ],
19
+ }
20
+
21
+ type Story = StoryObj<SearchInputProps>
22
+
23
+ export const Basic: Story = {}
24
+
25
+ export const Sizes: Story = {
26
+ render: () => {
27
+ return (
28
+ <Stack>
29
+ <SearchInput size="lg" />
30
+ <SearchInput size="md" />
31
+ <SearchInput size="sm" />
32
+ <SearchInput size="xs" />
33
+ </Stack>
34
+ )
35
+ },
36
+ }
37
+
38
+ export const Disabled: Story = {
39
+ args: {
40
+ disabled: true,
41
+ },
42
+ }
43
+
44
+ export const CustomIcons: Story = {
45
+ args: {
46
+ icon: <RiSearch2Line />,
47
+ resetIcon: <RiCloseLine />,
48
+ },
49
+ }
50
+
51
+ export const Controlled: Story = {
52
+ render: (props: any) => {
53
+ const [value, setValue] = useState('')
54
+ return (
55
+ <SearchInput
56
+ value={value}
57
+ onChange={({ target }) => setValue(target.value)}
58
+ onReset={() => setValue('')}
59
+ {...props}
60
+ />
61
+ )
62
+ },
63
+ }
@@ -0,0 +1,134 @@
1
+ import React, { forwardRef } from 'react'
2
+
3
+ import {
4
+ Group,
5
+ IconButton,
6
+ type IconButtonProps,
7
+ Input,
8
+ InputElement,
9
+ InputProps,
10
+ mergeRefs,
11
+ useControllableState,
12
+ } from '@chakra-ui/react'
13
+ import { callAll } from '@saas-ui/core/utils'
14
+
15
+ import { CloseIcon, SearchIcon } from '#components/icons/index.ts'
16
+
17
+ export interface SearchInputProps extends InputProps {
18
+ value?: string
19
+ defaultValue?: string
20
+ placeholder?: string
21
+ icon?: React.ReactElement
22
+ resetIcon?: React.ReactElement
23
+ endElement?: React.ReactElement
24
+ onReset?: () => void
25
+ }
26
+
27
+ export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
28
+ (props, ref) => {
29
+ const {
30
+ placeholder = 'Search',
31
+ value: valueProp,
32
+ defaultValue: defaultValueProp,
33
+ size,
34
+ variant,
35
+ width = 'full',
36
+ icon = <SearchIcon />,
37
+ resetIcon,
38
+ endElement: endElementProp,
39
+ onChange: onChangeProp,
40
+ onReset: onResetProp,
41
+ onKeyDown: onKeyDownProp,
42
+ disabled,
43
+ ...inputProps
44
+ } = props
45
+
46
+ const inputRef = React.useRef<HTMLInputElement>(null)
47
+
48
+ const [value, setValue] = useControllableState({
49
+ value: valueProp,
50
+ defaultValue: defaultValueProp,
51
+ })
52
+
53
+ const onChange = React.useCallback(
54
+ (e: React.ChangeEvent<HTMLInputElement>) => {
55
+ setValue(e.target.value)
56
+ },
57
+ [setValue],
58
+ )
59
+
60
+ const onKeyDown = React.useCallback(
61
+ (event: React.KeyboardEvent) => {
62
+ if (event.key === 'Escape') {
63
+ setValue('')
64
+ onReset()
65
+ }
66
+ },
67
+ [onResetProp, setValue],
68
+ )
69
+
70
+ const onReset = () => {
71
+ setValue('')
72
+ onResetProp?.()
73
+ inputRef.current?.focus()
74
+ }
75
+
76
+ const showReset = value && !props.disabled
77
+
78
+ const endElement = showReset ? (
79
+ <SearchInputResetButton size={size}>{resetIcon}</SearchInputResetButton>
80
+ ) : (
81
+ endElementProp
82
+ )
83
+
84
+ return (
85
+ <Group width={width}>
86
+ <InputElement
87
+ placement="start"
88
+ px="0"
89
+ aspectRatio="9/10"
90
+ fontSize={size}
91
+ >
92
+ {icon}
93
+ </InputElement>
94
+ <Input
95
+ type="text"
96
+ placeholder={placeholder}
97
+ variant={variant}
98
+ size={size}
99
+ value={value}
100
+ disabled={disabled}
101
+ ref={mergeRefs(ref, inputRef)}
102
+ onChange={callAll(onChange, onChangeProp)}
103
+ onKeyDown={callAll(onKeyDown, onKeyDownProp)}
104
+ ps="calc(var(--input-height) - var(--input-height) / 10)"
105
+ pe="calc(var(--input-height) - var(--input-height) / 10)"
106
+ {...inputProps}
107
+ />
108
+ <InputElement placement="end">{endElement}</InputElement>
109
+ </Group>
110
+ )
111
+ },
112
+ )
113
+
114
+ const SearchInputResetButton = forwardRef<HTMLButtonElement, IconButtonProps>(
115
+ (props, ref) => {
116
+ const { children = <CloseIcon />, ...rest } = props
117
+
118
+ return (
119
+ <IconButton
120
+ ref={ref}
121
+ variant="ghost"
122
+ aria-label="Reset search"
123
+ me="-2"
124
+ aspectRatio="square"
125
+ height="calc(100% - {spacing.2})"
126
+ {...rest}
127
+ >
128
+ {children}
129
+ </IconButton>
130
+ )
131
+ },
132
+ )
133
+
134
+ SearchInput.displayName = 'SearchInput'
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import { For, SegmentGroup } from "@chakra-ui/react"
4
+ import { forwardRef, useMemo } from "react"
5
+
6
+ interface Item {
7
+ value: string
8
+ label: React.ReactNode
9
+ disabled?: boolean
10
+ }
11
+
12
+ export interface SegmentedControlProps extends SegmentGroup.RootProps {
13
+ items: Array<string | Item>
14
+ }
15
+
16
+ function normalize(items: Array<string | Item>): Item[] {
17
+ return items.map((item) => {
18
+ if (typeof item === "string") return { value: item, label: item }
19
+ return item
20
+ })
21
+ }
22
+
23
+ export const SegmentedControl = forwardRef<
24
+ HTMLDivElement,
25
+ SegmentedControlProps
26
+ >(function SegmentedControl(props, ref) {
27
+ const { items, ...rest } = props
28
+ const data = useMemo(() => normalize(items), [items])
29
+
30
+ return (
31
+ <SegmentGroup.Root ref={ref} {...rest}>
32
+ <SegmentGroup.Indicator />
33
+ <For each={data}>
34
+ {(item) => (
35
+ <SegmentGroup.Item
36
+ key={item.value}
37
+ value={item.value}
38
+ disabled={item.disabled}
39
+ >
40
+ <SegmentGroup.ItemText>{item.label}</SegmentGroup.ItemText>
41
+ <SegmentGroup.ItemHiddenInput />
42
+ </SegmentGroup.Item>
43
+ )}
44
+ </For>
45
+ </SegmentGroup.Root>
46
+ )
47
+ })
@@ -0,0 +1 @@
1
+ export * as Select from './namespace'
@@ -0,0 +1,18 @@
1
+ export {
2
+ SelectRoot as Root,
3
+ SelectTrigger as Trigger,
4
+ SelectContent as Content,
5
+ SelectItem as Item,
6
+ SelectLabel as Label,
7
+ SelectItemGroup as ItemGroup,
8
+ SelectItemText as ItemText,
9
+ SelectValueText as ValueText,
10
+ } from './select.tsx'
11
+
12
+ export type {
13
+ SelectRootProps as RootProps,
14
+ SelectTriggerProps as TriggerProps,
15
+ SelectContentProps as ContentProps,
16
+ SelectItemGroupProps as ItemGroupProps,
17
+ SelectValueTextProps as ValueTextProps,
18
+ } from './select.tsx'
@@ -0,0 +1,135 @@
1
+ 'use client'
2
+
3
+ import { forwardRef } from 'react'
4
+
5
+ import type { CollectionItem } from '@chakra-ui/react'
6
+ import { Portal, Select as SelectPrimitive } from '@chakra-ui/react'
7
+
8
+ import { CloseButton } from '#components/close-button'
9
+
10
+ export interface SelectTriggerProps extends SelectPrimitive.ControlProps {
11
+ clearable?: boolean
12
+ }
13
+
14
+ export const SelectTrigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(
15
+ function SelectTrigger(props, ref) {
16
+ const { children, clearable, ...rest } = props
17
+ return (
18
+ <SelectPrimitive.Control {...rest}>
19
+ <SelectPrimitive.Trigger ref={ref}>{children}</SelectPrimitive.Trigger>
20
+ <SelectPrimitive.IndicatorGroup>
21
+ {clearable && <SelectClearTrigger />}
22
+ <SelectPrimitive.Indicator />
23
+ </SelectPrimitive.IndicatorGroup>
24
+ </SelectPrimitive.Control>
25
+ )
26
+ },
27
+ )
28
+
29
+ const SelectClearTrigger = forwardRef<
30
+ HTMLButtonElement,
31
+ SelectPrimitive.ClearTriggerProps
32
+ >(function SelectClearTrigger(props, ref) {
33
+ return (
34
+ <SelectPrimitive.ClearTrigger asChild {...props} ref={ref}>
35
+ <CloseButton
36
+ size="xs"
37
+ variant="plain"
38
+ focusVisibleRing="inside"
39
+ focusRingWidth="2px"
40
+ pointerEvents="auto"
41
+ />
42
+ </SelectPrimitive.ClearTrigger>
43
+ )
44
+ })
45
+
46
+ export interface SelectContentProps extends SelectPrimitive.ContentProps {
47
+ portalled?: boolean
48
+ portalRef?: React.RefObject<HTMLElement>
49
+ }
50
+
51
+ export const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
52
+ function SelectContent(props, ref) {
53
+ const { portalled = true, portalRef, ...rest } = props
54
+ return (
55
+ <Portal disabled={!portalled} container={portalRef}>
56
+ <SelectPrimitive.Positioner>
57
+ <SelectPrimitive.Content {...rest} ref={ref} />
58
+ </SelectPrimitive.Positioner>
59
+ </Portal>
60
+ )
61
+ },
62
+ )
63
+
64
+ export const SelectItem = forwardRef<HTMLDivElement, SelectPrimitive.ItemProps>(
65
+ function SelectItem(props, ref) {
66
+ const { item, children, ...rest } = props
67
+ return (
68
+ <SelectPrimitive.Item key={item.value} item={item} {...rest} ref={ref}>
69
+ {children}
70
+ <SelectPrimitive.ItemIndicator />
71
+ </SelectPrimitive.Item>
72
+ )
73
+ },
74
+ )
75
+
76
+ export interface SelectValueTextProps
77
+ extends Omit<SelectPrimitive.ValueTextProps, 'children'> {
78
+ children?(items: CollectionItem[]): React.ReactNode
79
+ }
80
+
81
+ export const SelectValueText = forwardRef<
82
+ HTMLSpanElement,
83
+ SelectValueTextProps
84
+ >(function SelectValueText(props, ref) {
85
+ const { children, ...rest } = props
86
+ return (
87
+ <SelectPrimitive.ValueText {...rest} ref={ref}>
88
+ <SelectPrimitive.Context>
89
+ {(select) => {
90
+ const items = select.selectedItems
91
+ if (items.length === 0) return props.placeholder
92
+ if (children) return children(items)
93
+ if (items.length === 1)
94
+ return select.collection.stringifyItem(items[0])
95
+ return `${items.length} selected`
96
+ }}
97
+ </SelectPrimitive.Context>
98
+ </SelectPrimitive.ValueText>
99
+ )
100
+ })
101
+
102
+ export interface SelectRootProps<T> extends SelectPrimitive.RootProps<T> {}
103
+
104
+ export const SelectRoot = forwardRef(function SelectRoot<
105
+ T extends CollectionItem,
106
+ >(props: SelectRootProps<T>, ref: React.Ref<HTMLDivElement>) {
107
+ return (
108
+ <SelectPrimitive.Root
109
+ {...props}
110
+ ref={ref}
111
+ positioning={{ sameWidth: true, ...props.positioning }}
112
+ />
113
+ )
114
+ }) as <T extends CollectionItem>(
115
+ props: SelectRootProps<T> & React.RefAttributes<HTMLDivElement>,
116
+ ) => React.ReactElement
117
+
118
+ export interface SelectItemGroupProps extends SelectPrimitive.ItemGroupProps {
119
+ label: React.ReactNode
120
+ }
121
+
122
+ export const SelectItemGroup = forwardRef<HTMLDivElement, SelectItemGroupProps>(
123
+ function SelectItemGroup(props, ref) {
124
+ const { children, label, ...rest } = props
125
+ return (
126
+ <SelectPrimitive.ItemGroup {...rest} ref={ref}>
127
+ <SelectPrimitive.ItemGroupLabel>{label}</SelectPrimitive.ItemGroupLabel>
128
+ {children}
129
+ </SelectPrimitive.ItemGroup>
130
+ )
131
+ },
132
+ )
133
+
134
+ export const SelectLabel = SelectPrimitive.Label
135
+ export const SelectItemText = SelectPrimitive.ItemText
@@ -0,0 +1,7 @@
1
+ export * as Sidebar from './namespace'
2
+
3
+ export {
4
+ useSidebar,
5
+ useSidebarStyles,
6
+ useSidebarItemStyles,
7
+ } from './sidebar.tsx'
@@ -0,0 +1,27 @@
1
+ export {
2
+ SidebarProvider as Provider,
3
+ SidebarRoot as Root,
4
+ SidebarTrigger as Trigger,
5
+ SidebarFlyoutTrigger as FlyoutTrigger,
6
+ SidebarBackdrop as Backdrop,
7
+ SidebarHeader as Header,
8
+ SidebarBody as Body,
9
+ SidebarFooter as Footer,
10
+ SidebarTrack as Track,
11
+ SidebarGroup as Group,
12
+ SidebarGroupHeader as GroupHeader,
13
+ SidebarGroupTitle as GroupTitle,
14
+ SidebarGroupEndElement as GroupEndElement,
15
+ SidebarGroupContent as GroupContent,
16
+ SidebarNavItem as NavItem,
17
+ SidebarNavButton as NavButton,
18
+ SidebarNavItemEndElement as NavItemEndElement,
19
+ } from './sidebar.tsx'
20
+
21
+ export type {
22
+ SidebarRootProps as RootProps,
23
+ SidebarNavButtonProps as NavButtonProps,
24
+ SidebarFlyoutTriggerProps as FlyoutTriggerProps,
25
+ SidebarProviderProps as ProviderProps,
26
+ SidebarTriggerProps as TriggerProps,
27
+ } from './sidebar.tsx'
@@ -0,0 +1,65 @@
1
+ import { defineSlotRecipe } from '@chakra-ui/react'
2
+
3
+ export const sidebarNavItemSlotRecipe = defineSlotRecipe({
4
+ className: 'sui-sidebar-nav-item',
5
+ slots: ['item', 'button'],
6
+ base: {
7
+ item: {
8
+ position: 'relative',
9
+ },
10
+ button: {
11
+ display: 'flex',
12
+ alignItems: 'center',
13
+ gap: 2,
14
+ isolation: 'isolate',
15
+ width: '100%',
16
+ textOverflow: 'ellipsis',
17
+ overflow: 'hidden',
18
+ whiteSpace: 'nowrap',
19
+ cursor: 'button',
20
+ transitionProperty: 'common',
21
+ transitionDuration: 'fast',
22
+ focusVisibleRing: 'outside',
23
+ '& > svg': {
24
+ boxSize: 4,
25
+ },
26
+ },
27
+ },
28
+ variants: {
29
+ variant: {
30
+ muted: {
31
+ button: {
32
+ bg: 'transparent',
33
+ color: 'sidebar.accent.fg/85',
34
+ _hover: {
35
+ bg: 'sidebar.accent.bg/90',
36
+ color: 'sidebar.accent.fg',
37
+ },
38
+ _active: {
39
+ bg: 'sidebar.accent.bg',
40
+ color: 'sidebar.accent.fg',
41
+ },
42
+ },
43
+ },
44
+ },
45
+ size: {
46
+ md: {
47
+ item: {
48
+ fontSize: 'sm',
49
+ },
50
+ button: {
51
+ borderRadius: 'md',
52
+ px: 2,
53
+ height: 8,
54
+ '&:has(:nth-child(3))': {
55
+ pe: 0,
56
+ },
57
+ },
58
+ },
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ variant: 'muted',
63
+ size: 'md',
64
+ },
65
+ })