@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,207 @@
1
+ "use client"
2
+
3
+ import type { ButtonProps, TextProps } from "@chakra-ui/react"
4
+ import {
5
+ Button,
6
+ Pagination as ChakraPagination,
7
+ IconButton,
8
+ Text,
9
+ createContext,
10
+ usePaginationContext,
11
+ } from "@chakra-ui/react"
12
+ import { forwardRef, useMemo } from "react"
13
+ import {
14
+ HiChevronLeft,
15
+ HiChevronRight,
16
+ HiMiniEllipsisHorizontal,
17
+ } from "react-icons/hi2"
18
+ import { LinkButton } from "./link-button"
19
+
20
+ interface ButtonVariantMap {
21
+ current: ButtonProps["variant"]
22
+ default: ButtonProps["variant"]
23
+ ellipsis: ButtonProps["variant"]
24
+ }
25
+
26
+ type PaginationVariant = "outline" | "solid" | "subtle"
27
+
28
+ interface ButtonVariantContext {
29
+ size: ButtonProps["size"]
30
+ variantMap: ButtonVariantMap
31
+ getHref?: (page: number) => string
32
+ }
33
+
34
+ const [RootPropsProvider, useRootProps] = createContext<ButtonVariantContext>({
35
+ name: "RootPropsProvider",
36
+ })
37
+
38
+ export interface PaginationRootProps
39
+ extends Omit<ChakraPagination.RootProps, "type"> {
40
+ size?: ButtonProps["size"]
41
+ variant?: PaginationVariant
42
+ getHref?: (page: number) => string
43
+ }
44
+
45
+ const variantMap: Record<PaginationVariant, ButtonVariantMap> = {
46
+ outline: { default: "ghost", ellipsis: "plain", current: "outline" },
47
+ solid: { default: "outline", ellipsis: "outline", current: "solid" },
48
+ subtle: { default: "ghost", ellipsis: "plain", current: "subtle" },
49
+ }
50
+
51
+ export const PaginationRoot = forwardRef<HTMLDivElement, PaginationRootProps>(
52
+ function PaginationRoot(props, ref) {
53
+ const { size = "sm", variant = "outline", getHref, ...rest } = props
54
+ return (
55
+ <RootPropsProvider
56
+ value={{ size, variantMap: variantMap[variant], getHref }}
57
+ >
58
+ <ChakraPagination.Root
59
+ ref={ref}
60
+ type={getHref ? "link" : "button"}
61
+ {...rest}
62
+ />
63
+ </RootPropsProvider>
64
+ )
65
+ },
66
+ )
67
+
68
+ export const PaginationEllipsis = forwardRef<
69
+ HTMLDivElement,
70
+ ChakraPagination.EllipsisProps
71
+ >(function PaginationEllipsis(props, ref) {
72
+ const { size, variantMap } = useRootProps()
73
+ return (
74
+ <ChakraPagination.Ellipsis ref={ref} {...props} asChild>
75
+ <Button as="span" variant={variantMap.ellipsis} size={size}>
76
+ <HiMiniEllipsisHorizontal />
77
+ </Button>
78
+ </ChakraPagination.Ellipsis>
79
+ )
80
+ })
81
+
82
+ export const PaginationItem = forwardRef<
83
+ HTMLButtonElement,
84
+ ChakraPagination.ItemProps
85
+ >(function PaginationItem(props, ref) {
86
+ const { page } = usePaginationContext()
87
+ const { size, variantMap, getHref } = useRootProps()
88
+
89
+ const current = page === props.value
90
+ const variant = current ? variantMap.current : variantMap.default
91
+
92
+ if (getHref) {
93
+ return (
94
+ <LinkButton href={getHref(props.value)} variant={variant} size={size}>
95
+ {props.value}
96
+ </LinkButton>
97
+ )
98
+ }
99
+
100
+ return (
101
+ <ChakraPagination.Item ref={ref} {...props} asChild>
102
+ <Button variant={variant} size={size}>
103
+ {props.value}
104
+ </Button>
105
+ </ChakraPagination.Item>
106
+ )
107
+ })
108
+
109
+ export const PaginationPrevTrigger = forwardRef<
110
+ HTMLButtonElement,
111
+ ChakraPagination.PrevTriggerProps
112
+ >(function PaginationPrevTrigger(props, ref) {
113
+ const { size, variantMap, getHref } = useRootProps()
114
+ const { previousPage } = usePaginationContext()
115
+
116
+ if (getHref) {
117
+ return (
118
+ <LinkButton
119
+ href={previousPage != null ? getHref(previousPage) : undefined}
120
+ variant={variantMap.default}
121
+ size={size}
122
+ >
123
+ <HiChevronLeft />
124
+ </LinkButton>
125
+ )
126
+ }
127
+
128
+ return (
129
+ <ChakraPagination.PrevTrigger ref={ref} asChild {...props}>
130
+ <IconButton variant={variantMap.default} size={size}>
131
+ <HiChevronLeft />
132
+ </IconButton>
133
+ </ChakraPagination.PrevTrigger>
134
+ )
135
+ })
136
+
137
+ export const PaginationNextTrigger = forwardRef<
138
+ HTMLButtonElement,
139
+ ChakraPagination.NextTriggerProps
140
+ >(function PaginationNextTrigger(props, ref) {
141
+ const { size, variantMap, getHref } = useRootProps()
142
+ const { nextPage } = usePaginationContext()
143
+
144
+ if (getHref) {
145
+ return (
146
+ <LinkButton
147
+ href={nextPage != null ? getHref(nextPage) : undefined}
148
+ variant={variantMap.default}
149
+ size={size}
150
+ >
151
+ <HiChevronRight />
152
+ </LinkButton>
153
+ )
154
+ }
155
+
156
+ return (
157
+ <ChakraPagination.NextTrigger ref={ref} asChild {...props}>
158
+ <IconButton variant={variantMap.default} size={size}>
159
+ <HiChevronRight />
160
+ </IconButton>
161
+ </ChakraPagination.NextTrigger>
162
+ )
163
+ })
164
+
165
+ export const PaginationItems = (props: React.HTMLAttributes<HTMLElement>) => {
166
+ return (
167
+ <ChakraPagination.Context>
168
+ {({ pages }) =>
169
+ pages.map((page, index) => {
170
+ return page.type === "ellipsis" ? (
171
+ <PaginationEllipsis key={index} index={index} {...props} />
172
+ ) : (
173
+ <PaginationItem
174
+ key={index}
175
+ type="page"
176
+ value={page.value}
177
+ {...props}
178
+ />
179
+ )
180
+ })
181
+ }
182
+ </ChakraPagination.Context>
183
+ )
184
+ }
185
+
186
+ interface PageTextProps extends TextProps {
187
+ format?: "short" | "compact" | "long"
188
+ }
189
+
190
+ export const PaginationPageText = forwardRef<
191
+ HTMLParagraphElement,
192
+ PageTextProps
193
+ >(function PaginationPageText(props, ref) {
194
+ const { format = "compact", ...rest } = props
195
+ const { page, pages, pageRange, count } = usePaginationContext()
196
+ const content = useMemo(() => {
197
+ if (format === "short") return `${page} / ${pages.length}`
198
+ if (format === "compact") return `${page} of ${pages.length}`
199
+ return `${pageRange.start + 1} - ${pageRange.end} of ${count}`
200
+ }, [format, page, pages.length, pageRange, count])
201
+
202
+ return (
203
+ <Text fontWeight="medium" ref={ref} {...rest}>
204
+ {content}
205
+ </Text>
206
+ )
207
+ })
@@ -0,0 +1,2 @@
1
+ export { PasswordInput } from './password-input'
2
+ export type { PasswordInputProps } from './password-input'
@@ -0,0 +1,98 @@
1
+ 'use client'
2
+
3
+ import { forwardRef, useRef } from 'react'
4
+
5
+ import type {
6
+ ButtonProps,
7
+ GroupProps,
8
+ InputProps,
9
+ StackProps,
10
+ } from '@chakra-ui/react'
11
+ import {
12
+ IconButton,
13
+ Input,
14
+ mergeRefs,
15
+ useControllableState,
16
+ } from '@chakra-ui/react'
17
+ import { LuEye, LuEyeOff } from 'react-icons/lu'
18
+
19
+ import { InputGroup } from '../input-group'
20
+
21
+ export interface PasswordInputProps
22
+ extends InputProps,
23
+ PasswordVisibilityProps {
24
+ rootProps?: GroupProps
25
+ }
26
+
27
+ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
28
+ function PasswordInput(props, ref) {
29
+ const {
30
+ rootProps,
31
+ defaultVisible,
32
+ visible: visibleProp,
33
+ onVisibleChange,
34
+ visibilityIcon = { on: <LuEye />, off: <LuEyeOff /> },
35
+ ...rest
36
+ } = props
37
+
38
+ const [visible, setVisible] = useControllableState({
39
+ value: visibleProp,
40
+ defaultValue: defaultVisible || false,
41
+ onChange: onVisibleChange,
42
+ })
43
+
44
+ const inputRef = useRef<HTMLInputElement>(null)
45
+
46
+ return (
47
+ <InputGroup
48
+ width="full"
49
+ endElement={
50
+ <VisibilityTrigger
51
+ disabled={rest.disabled}
52
+ onPointerDown={(e) => {
53
+ if (rest.disabled) return
54
+ if (e.button !== 0) return
55
+ e.preventDefault()
56
+ setVisible(!visible)
57
+ }}
58
+ >
59
+ {visible ? visibilityIcon.off : visibilityIcon.on}
60
+ </VisibilityTrigger>
61
+ }
62
+ {...rootProps}
63
+ >
64
+ <Input
65
+ {...rest}
66
+ ref={mergeRefs(ref, inputRef)}
67
+ type={visible ? 'text' : 'password'}
68
+ />
69
+ </InputGroup>
70
+ )
71
+ },
72
+ )
73
+
74
+ export interface PasswordVisibilityProps {
75
+ defaultVisible?: boolean
76
+ visible?: boolean
77
+ onVisibleChange?: (visible: boolean) => void
78
+ visibilityIcon?: { on: React.ReactNode; off: React.ReactNode }
79
+ }
80
+
81
+ const VisibilityTrigger = forwardRef<HTMLButtonElement, ButtonProps>(
82
+ function VisibilityTrigger(props, ref) {
83
+ return (
84
+ <IconButton
85
+ tabIndex={-1}
86
+ ref={ref}
87
+ me="-2"
88
+ aspectRatio="square"
89
+ size="sm"
90
+ variant="ghost"
91
+ colorPalette="gray"
92
+ height="calc(100% - {spacing.2})"
93
+ aria-label="Toggle password visibility"
94
+ {...props}
95
+ />
96
+ )
97
+ },
98
+ )
@@ -0,0 +1,2 @@
1
+ export { Persona, PersonaAvatar } from './persona.tsx'
2
+ export type { PersonaProps, PersonaAvatarProps } from './persona.tsx'
@@ -0,0 +1,18 @@
1
+ export {
2
+ PersonaRoot as Root,
3
+ PersonaAvatar as Avatar,
4
+ PersonaPresenceBadge as PresenceBadge,
5
+ PersonaDetails as Details,
6
+ PersonaLabel as Label,
7
+ PersonaSecondaryLabel as SecondaryLabel,
8
+ PersonaTertiaryLabel as TertiaryLabel,
9
+ } from './persona-primitive.tsx'
10
+
11
+ export type {
12
+ PersonaRootProps as RootProps,
13
+ PersonaAvatarProps as AvatarProps,
14
+ PersonaPresenceBadgeProps as PresenceBadgeProps,
15
+ PersonaDetailsProps as DetailsProps,
16
+ PersonaLabelProps as LabelProps,
17
+ Presence,
18
+ } from './persona-primitive.tsx'
@@ -0,0 +1,220 @@
1
+ import React, { forwardRef } from 'react'
2
+
3
+ import {
4
+ Avatar,
5
+ type AvatarRootProps,
6
+ HTMLChakraProps,
7
+ type ImageProps,
8
+ SlotRecipeProps,
9
+ chakra,
10
+ createSlotRecipeContext,
11
+ } from '@chakra-ui/react'
12
+ import { dataAttr } from '@saas-ui/core/utils'
13
+
14
+ const {
15
+ useStyles: usePersonaStyles,
16
+ withProvider,
17
+ withContext,
18
+ } = createSlotRecipeContext({
19
+ key: 'persona',
20
+ })
21
+
22
+ export { usePersonaStyles }
23
+
24
+ export type Presence = 'online' | 'offline' | 'busy' | 'dnd' | 'away'
25
+
26
+ interface PresenceConfig {
27
+ label: string
28
+ color: string
29
+ }
30
+
31
+ export type PresenceOptions<P extends string = Presence> = Record<
32
+ P,
33
+ PresenceConfig
34
+ >
35
+
36
+ /**
37
+ * The presence configuration object.
38
+ *
39
+ * Default presence values: online, offline, busy, dnd, away
40
+ *
41
+ * You can overwrite colors in the theme semantic tokens.
42
+ * theme.semanticTokens.colors['presence.online'] = 'cyan.500'
43
+ *
44
+ * Or add a custom presence value
45
+ * theme.semanticTokens.colors['presence.vacay'] = 'blue.500'
46
+ *
47
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
48
+ */
49
+ export const defaultPresenceOptions: PresenceOptions = {
50
+ online: {
51
+ label: 'Online',
52
+ color: 'presence.online',
53
+ },
54
+ offline: {
55
+ label: 'Offline',
56
+ color: 'presence.offline',
57
+ },
58
+ busy: {
59
+ label: 'Busy',
60
+ color: 'presence.busy',
61
+ },
62
+ dnd: {
63
+ label: 'Do-not-disturb',
64
+ color: 'presence.dnd',
65
+ },
66
+ away: {
67
+ label: 'Away',
68
+ color: 'presence.away',
69
+ },
70
+ }
71
+
72
+ export interface PersonaRootProps
73
+ extends HTMLChakraProps<'div'>,
74
+ SlotRecipeProps<'persona'> {
75
+ /**
76
+ * Indicates that a person is out of office. Changes the presence badge style.
77
+ */
78
+ outOfOffice?: boolean
79
+ /**
80
+ * The presence status of the person
81
+ */
82
+ presence?: Presence
83
+ }
84
+
85
+ /**
86
+ * The root component that provides context and styles.
87
+ *
88
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
89
+ */
90
+ export const PersonaRoot = withProvider<HTMLDivElement, PersonaRootProps>(
91
+ forwardRef((props, ref) => {
92
+ const { outOfOffice, presence, ...rest } = props
93
+
94
+ return (
95
+ <chakra.div
96
+ ref={ref}
97
+ {...rest}
98
+ data-out-of-office={dataAttr(outOfOffice)}
99
+ data-presence={presence}
100
+ css={[
101
+ presence
102
+ ? {
103
+ '--persona-presence': `colors.presence.${presence}`,
104
+ }
105
+ : undefined,
106
+ rest.css,
107
+ ]}
108
+ />
109
+ )
110
+ }),
111
+ 'root',
112
+ )
113
+
114
+ interface PersonaAvatarOptions {
115
+ /**
116
+ * The name of the person in the avatar.
117
+ *
118
+ * - if `src` has loaded, the name will be used as the `alt` attribute of the `img`
119
+ * - If `src` is not loaded, the name will be used to create the initials
120
+ */
121
+ name?: string
122
+ }
123
+
124
+ export interface PersonaAvatarProps
125
+ extends PersonaAvatarOptions,
126
+ AvatarRootProps {
127
+ src?: string
128
+ srcSet?: string
129
+ loading?: ImageProps['loading']
130
+ icon?: React.ReactElement
131
+ fallback?: React.ReactNode
132
+ getInitials?: (name?: string | null) => string | null
133
+ }
134
+
135
+ /**
136
+ * An avatar with optional status badge.
137
+ *
138
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
139
+ */
140
+ export const PersonaAvatar = forwardRef<HTMLDivElement, PersonaAvatarProps>(
141
+ (props, ref) => {
142
+ const {
143
+ name,
144
+ getInitials = (name?: string | null) => name?.[0],
145
+ icon,
146
+ loading,
147
+ onError,
148
+ src,
149
+ srcSet,
150
+ children,
151
+ ...rest
152
+ } = props
153
+
154
+ return (
155
+ <Avatar.Root ref={ref} {...rest}>
156
+ <Avatar.Fallback>{getInitials(name)}</Avatar.Fallback>
157
+ <Avatar.Image
158
+ src={src}
159
+ srcSet={srcSet}
160
+ loading={loading}
161
+ onError={onError}
162
+ />
163
+ {children}
164
+ </Avatar.Root>
165
+ )
166
+ },
167
+ )
168
+
169
+ export interface PersonaPresenceBadgeProps extends HTMLChakraProps<'span'> {}
170
+
171
+ export const PersonaPresenceBadge = withContext<
172
+ HTMLSpanElement,
173
+ PersonaPresenceBadgeProps
174
+ >('span', 'presence')
175
+
176
+ export interface PersonaDetailsProps extends HTMLChakraProps<'div'> {}
177
+
178
+ /**
179
+ * Wrapper component for the labels.
180
+ *
181
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
182
+ */
183
+ export const PersonaDetails = withContext<HTMLDivElement, PersonaDetailsProps>(
184
+ 'div',
185
+ 'details',
186
+ )
187
+
188
+ export interface PersonaLabelProps extends HTMLChakraProps<'span'> {}
189
+
190
+ /**
191
+ * The main label, usually a name.
192
+ *
193
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
194
+ */
195
+ export const PersonaLabel = withContext<HTMLSpanElement, PersonaLabelProps>(
196
+ 'span',
197
+ 'label',
198
+ )
199
+
200
+ PersonaLabel.displayName = 'PersonaLabel'
201
+
202
+ /**
203
+ * The secondary label, usually the role of a person.
204
+ *
205
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
206
+ */
207
+ export const PersonaSecondaryLabel = withContext<
208
+ HTMLSpanElement,
209
+ PersonaLabelProps
210
+ >('span', 'secondaryLabel')
211
+
212
+ /**
213
+ * The tertiary label, typically a status message.
214
+ *
215
+ * @see Docs https://saas-ui.dev/docs/components/data-display/persona
216
+ */
217
+ export const PersonaTertiaryLabel = withContext<
218
+ HTMLSpanElement,
219
+ PersonaLabelProps
220
+ >('span', 'tertiaryLabel')
@@ -0,0 +1,94 @@
1
+ import { defineSlotRecipe, defineStyle } from '@chakra-ui/react'
2
+
3
+ const baseStyleLabel = defineStyle({
4
+ overflow: 'hidden',
5
+ whiteSpace: 'nowrap',
6
+ textOverflow: 'ellipsis',
7
+ minW: 0,
8
+ })
9
+
10
+ export const personaSlotRecipe = defineSlotRecipe({
11
+ className: 'sui-persona',
12
+ slots: [
13
+ 'root',
14
+ 'avatar',
15
+ 'presence',
16
+ 'details',
17
+ 'label',
18
+ 'secondaryLabel',
19
+ 'tertiaryLabel',
20
+ ],
21
+ base: {
22
+ root: {
23
+ display: 'flex',
24
+ flexDirection: 'row',
25
+ alignItems: 'center',
26
+ },
27
+ presence: {
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ justifyContent: 'center',
31
+ position: 'absolute',
32
+ bottom: 0,
33
+ right: 0,
34
+ boxSize: '1em',
35
+ transform: 'translate(15%, 15%)',
36
+ borderWidth: '0.15em',
37
+ borderRadius: '50%',
38
+ borderColor: 'bg.panel',
39
+ bg: 'var(--persona-presence)',
40
+ },
41
+ details: {
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ minW: 0,
45
+ lineHeight: 'short',
46
+ },
47
+ label: baseStyleLabel,
48
+ secondaryLabel: {
49
+ ...baseStyleLabel,
50
+ color: 'fg.muted',
51
+ },
52
+ tertiaryLabel: {
53
+ ...baseStyleLabel,
54
+ color: 'fg.muted',
55
+ },
56
+ },
57
+ variants: {
58
+ size: {
59
+ xs: {
60
+ details: { ms: 2 },
61
+ label: { fontSize: 'xs' },
62
+ secondaryLabel: { display: 'none' },
63
+ tertiaryLabel: { display: 'none' },
64
+ },
65
+ sm: {
66
+ details: { ms: 2 },
67
+ label: { fontSize: 'sm' },
68
+ secondaryLabel: { fontSize: 'xs' },
69
+ tertiaryLabel: { display: 'none' },
70
+ },
71
+ md: {
72
+ details: { ms: 2 },
73
+ label: { fontSize: 'sm' },
74
+ secondaryLabel: { fontSize: 'xs' },
75
+ tertiaryLabel: { display: 'none' },
76
+ },
77
+ lg: {
78
+ details: { ms: 3 },
79
+ label: { fontSize: 'md' },
80
+ secondaryLabel: { fontSize: 'sm' },
81
+ tertiaryLabel: { fontSize: 'sm' },
82
+ },
83
+ xl: {
84
+ details: { ms: 4 },
85
+ label: { fontSize: 'lg' },
86
+ secondaryLabel: { fontSize: 'md' },
87
+ tertiaryLabel: { fontSize: 'md' },
88
+ },
89
+ },
90
+ },
91
+ defaultVariants: {
92
+ size: 'md',
93
+ },
94
+ })