@saas-ui/react 2.11.2 → 3.0.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ })