@arolariu/components 1.0.0 → 1.1.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 (218) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/EXAMPLES.md +2510 -0
  3. package/dist/components/ui/alert-dialog.d.ts +4 -16
  4. package/dist/components/ui/alert-dialog.d.ts.map +1 -1
  5. package/dist/components/ui/alert-dialog.js +18 -14
  6. package/dist/components/ui/alert-dialog.js.map +1 -1
  7. package/dist/components/ui/avatar.d.ts +3 -12
  8. package/dist/components/ui/avatar.d.ts.map +1 -1
  9. package/dist/components/ui/avatar.js +18 -15
  10. package/dist/components/ui/avatar.js.map +1 -1
  11. package/dist/components/ui/button-group.d.ts +1 -1
  12. package/dist/components/ui/button-group.d.ts.map +1 -1
  13. package/dist/components/ui/calendar.d.ts +1 -4
  14. package/dist/components/ui/calendar.d.ts.map +1 -1
  15. package/dist/components/ui/calendar.js +7 -7
  16. package/dist/components/ui/calendar.js.map +1 -1
  17. package/dist/components/ui/carousel.d.ts.map +1 -1
  18. package/dist/components/ui/carousel.js.map +1 -1
  19. package/dist/components/ui/chart.d.ts.map +1 -1
  20. package/dist/components/ui/chart.js +125 -59
  21. package/dist/components/ui/chart.js.map +1 -1
  22. package/dist/components/ui/checkbox-group.d.ts +2 -6
  23. package/dist/components/ui/checkbox-group.d.ts.map +1 -1
  24. package/dist/components/ui/checkbox-group.js +8 -7
  25. package/dist/components/ui/checkbox-group.js.map +1 -1
  26. package/dist/components/ui/checkbox.d.ts +3 -1
  27. package/dist/components/ui/checkbox.d.ts.map +1 -1
  28. package/dist/components/ui/checkbox.js +4 -1
  29. package/dist/components/ui/checkbox.js.map +1 -1
  30. package/dist/components/ui/collapsible.d.ts.map +1 -1
  31. package/dist/components/ui/collapsible.js.map +1 -1
  32. package/dist/components/ui/combobox.d.ts +335 -0
  33. package/dist/components/ui/combobox.d.ts.map +1 -0
  34. package/dist/components/ui/combobox.js +206 -0
  35. package/dist/components/ui/combobox.js.map +1 -0
  36. package/dist/components/ui/combobox.module.js +23 -0
  37. package/dist/components/ui/combobox.module.js.map +1 -0
  38. package/dist/components/ui/combobox_module.css +142 -0
  39. package/dist/components/ui/combobox_module.css.map +1 -0
  40. package/dist/components/ui/command.d.ts.map +1 -1
  41. package/dist/components/ui/command.js +25 -16
  42. package/dist/components/ui/command.js.map +1 -1
  43. package/dist/components/ui/context-menu.d.ts.map +1 -1
  44. package/dist/components/ui/context-menu.js.map +1 -1
  45. package/dist/components/ui/drawer.d.ts.map +1 -1
  46. package/dist/components/ui/drawer.js.map +1 -1
  47. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  48. package/dist/components/ui/dropdown-menu.js.map +1 -1
  49. package/dist/components/ui/dropdrawer.d.ts +10 -16
  50. package/dist/components/ui/dropdrawer.d.ts.map +1 -1
  51. package/dist/components/ui/dropdrawer.js +28 -20
  52. package/dist/components/ui/dropdrawer.js.map +1 -1
  53. package/dist/components/ui/item.d.ts +1 -1
  54. package/dist/components/ui/item.d.ts.map +1 -1
  55. package/dist/components/ui/menubar.d.ts +11 -13
  56. package/dist/components/ui/menubar.d.ts.map +1 -1
  57. package/dist/components/ui/menubar.js.map +1 -1
  58. package/dist/components/ui/meter.d.ts +8 -24
  59. package/dist/components/ui/meter.d.ts.map +1 -1
  60. package/dist/components/ui/meter.js +23 -19
  61. package/dist/components/ui/meter.js.map +1 -1
  62. package/dist/components/ui/navigation-menu.d.ts +3 -12
  63. package/dist/components/ui/navigation-menu.d.ts.map +1 -1
  64. package/dist/components/ui/navigation-menu.js +14 -11
  65. package/dist/components/ui/navigation-menu.js.map +1 -1
  66. package/dist/components/ui/number-field.d.ts +6 -12
  67. package/dist/components/ui/number-field.d.ts.map +1 -1
  68. package/dist/components/ui/number-field.js.map +1 -1
  69. package/dist/components/ui/progress.d.ts +1 -4
  70. package/dist/components/ui/progress.d.ts.map +1 -1
  71. package/dist/components/ui/progress.js +10 -9
  72. package/dist/components/ui/progress.js.map +1 -1
  73. package/dist/components/ui/radio-group.d.ts +2 -4
  74. package/dist/components/ui/radio-group.d.ts.map +1 -1
  75. package/dist/components/ui/radio-group.js.map +1 -1
  76. package/dist/components/ui/resizable.d.ts +3 -3
  77. package/dist/components/ui/resizable.d.ts.map +1 -1
  78. package/dist/components/ui/resizable.js.map +1 -1
  79. package/dist/components/ui/scratcher.d.ts +1 -1
  80. package/dist/components/ui/scratcher.d.ts.map +1 -1
  81. package/dist/components/ui/scratcher.js +5 -4
  82. package/dist/components/ui/scratcher.js.map +1 -1
  83. package/dist/components/ui/scroll-area.d.ts +2 -4
  84. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  85. package/dist/components/ui/scroll-area.js.map +1 -1
  86. package/dist/components/ui/separator.d.ts +1 -4
  87. package/dist/components/ui/separator.d.ts.map +1 -1
  88. package/dist/components/ui/separator.js +9 -8
  89. package/dist/components/ui/separator.js.map +1 -1
  90. package/dist/components/ui/sheet.d.ts.map +1 -1
  91. package/dist/components/ui/sheet.js.map +1 -1
  92. package/dist/components/ui/sidebar.d.ts +1 -1
  93. package/dist/components/ui/sidebar.d.ts.map +1 -1
  94. package/dist/components/ui/sidebar.js.map +1 -1
  95. package/dist/components/ui/sonner.d.ts +5 -4
  96. package/dist/components/ui/sonner.d.ts.map +1 -1
  97. package/dist/components/ui/sonner.js +7 -6
  98. package/dist/components/ui/sonner.js.map +1 -1
  99. package/dist/components/ui/toggle-group.d.ts +2 -8
  100. package/dist/components/ui/toggle-group.d.ts.map +1 -1
  101. package/dist/components/ui/toggle-group.js +12 -10
  102. package/dist/components/ui/toggle-group.js.map +1 -1
  103. package/dist/components/ui/toolbar.d.ts +10 -30
  104. package/dist/components/ui/toolbar.d.ts.map +1 -1
  105. package/dist/components/ui/toolbar.js +28 -23
  106. package/dist/components/ui/toolbar.js.map +1 -1
  107. package/dist/hooks/useClipboard.d.ts +77 -0
  108. package/dist/hooks/useClipboard.d.ts.map +1 -0
  109. package/dist/hooks/useClipboard.js +42 -0
  110. package/dist/hooks/useClipboard.js.map +1 -0
  111. package/dist/hooks/useControllableState.d.ts +54 -0
  112. package/dist/hooks/useControllableState.d.ts.map +1 -0
  113. package/dist/hooks/useControllableState.js +29 -0
  114. package/dist/hooks/useControllableState.js.map +1 -0
  115. package/dist/hooks/useDebounce.d.ts +33 -0
  116. package/dist/hooks/useDebounce.d.ts.map +1 -0
  117. package/dist/hooks/useDebounce.js +20 -0
  118. package/dist/hooks/useDebounce.js.map +1 -0
  119. package/dist/hooks/useEventCallback.d.ts +34 -0
  120. package/dist/hooks/useEventCallback.d.ts.map +1 -0
  121. package/dist/hooks/useEventCallback.js +12 -0
  122. package/dist/hooks/useEventCallback.js.map +1 -0
  123. package/dist/hooks/useId.d.ts +30 -0
  124. package/dist/hooks/useId.d.ts.map +1 -0
  125. package/dist/hooks/useId.js +9 -0
  126. package/dist/hooks/useId.js.map +1 -0
  127. package/dist/hooks/useIntersectionObserver.d.ts +51 -0
  128. package/dist/hooks/useIntersectionObserver.d.ts.map +1 -0
  129. package/dist/hooks/useIntersectionObserver.js +25 -0
  130. package/dist/hooks/useIntersectionObserver.js.map +1 -0
  131. package/dist/hooks/useInterval.d.ts +55 -0
  132. package/dist/hooks/useInterval.d.ts.map +1 -0
  133. package/dist/hooks/useInterval.js +24 -0
  134. package/dist/hooks/useInterval.js.map +1 -0
  135. package/dist/hooks/useLocalStorage.d.ts +43 -0
  136. package/dist/hooks/useLocalStorage.d.ts.map +1 -0
  137. package/dist/hooks/useLocalStorage.js +53 -0
  138. package/dist/hooks/useLocalStorage.js.map +1 -0
  139. package/dist/hooks/useMergedRefs.d.ts +27 -0
  140. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  141. package/dist/hooks/useMergedRefs.js +11 -0
  142. package/dist/hooks/useMergedRefs.js.map +1 -0
  143. package/dist/hooks/useOnClickOutside.d.ts +32 -0
  144. package/dist/hooks/useOnClickOutside.d.ts.map +1 -0
  145. package/dist/hooks/useOnClickOutside.js +23 -0
  146. package/dist/hooks/useOnClickOutside.js.map +1 -0
  147. package/dist/hooks/usePrevious.d.ts +33 -0
  148. package/dist/hooks/usePrevious.d.ts.map +1 -0
  149. package/dist/hooks/usePrevious.js +14 -0
  150. package/dist/hooks/usePrevious.js.map +1 -0
  151. package/dist/hooks/useThrottle.d.ts +37 -0
  152. package/dist/hooks/useThrottle.d.ts.map +1 -0
  153. package/dist/hooks/useThrottle.js +34 -0
  154. package/dist/hooks/useThrottle.js.map +1 -0
  155. package/dist/hooks/useTimeout.d.ts +28 -0
  156. package/dist/hooks/useTimeout.d.ts.map +1 -0
  157. package/dist/hooks/useTimeout.js +24 -0
  158. package/dist/hooks/useTimeout.js.map +1 -0
  159. package/dist/index.d.ts +14 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +14 -0
  162. package/dist/lib/utilities.d.ts +2 -3
  163. package/dist/lib/utilities.d.ts.map +1 -1
  164. package/dist/lib/utilities.js.map +1 -1
  165. package/dist/motion/tokens.js +5 -5
  166. package/dist/motion/tokens.js.map +1 -1
  167. package/dist/rslib-runtime.js +39 -0
  168. package/dist/rslib-runtime.js.map +1 -0
  169. package/package.json +82 -3
  170. package/src/components/ui/alert-dialog.tsx +15 -8
  171. package/src/components/ui/avatar.tsx +9 -6
  172. package/src/components/ui/calendar.tsx +7 -13
  173. package/src/components/ui/carousel.tsx +2 -0
  174. package/src/components/ui/chart.tsx +63 -60
  175. package/src/components/ui/checkbox-group.tsx +4 -5
  176. package/src/components/ui/checkbox.tsx +10 -2
  177. package/src/components/ui/collapsible.tsx +1 -0
  178. package/src/components/ui/combobox.module.css +158 -0
  179. package/src/components/ui/combobox.tsx +569 -0
  180. package/src/components/ui/command.tsx +31 -15
  181. package/src/components/ui/context-menu.tsx +3 -0
  182. package/src/components/ui/drawer.tsx +2 -0
  183. package/src/components/ui/dropdown-menu.tsx +3 -0
  184. package/src/components/ui/dropdrawer.tsx +80 -62
  185. package/src/components/ui/menubar.tsx +9 -10
  186. package/src/components/ui/meter.tsx +16 -17
  187. package/src/components/ui/navigation-menu.tsx +41 -33
  188. package/src/components/ui/number-field.tsx +6 -13
  189. package/src/components/ui/progress.tsx +3 -2
  190. package/src/components/ui/radio-group.tsx +2 -5
  191. package/src/components/ui/resizable.tsx +2 -2
  192. package/src/components/ui/scratcher.tsx +6 -10
  193. package/src/components/ui/scroll-area.tsx +2 -5
  194. package/src/components/ui/separator.tsx +4 -3
  195. package/src/components/ui/sheet.tsx +3 -0
  196. package/src/components/ui/sidebar.tsx +1 -0
  197. package/src/components/ui/sonner.tsx +20 -12
  198. package/src/components/ui/toggle-group.tsx +6 -4
  199. package/src/components/ui/toolbar.tsx +20 -21
  200. package/src/hooks/useClipboard.tsx +137 -0
  201. package/src/hooks/useControllableState.tsx +81 -0
  202. package/src/hooks/useDebounce.tsx +50 -0
  203. package/src/hooks/useEventCallback.tsx +47 -0
  204. package/src/hooks/useId.tsx +36 -0
  205. package/src/hooks/useIntersectionObserver.tsx +81 -0
  206. package/src/hooks/useInterval.tsx +80 -0
  207. package/src/hooks/useLocalStorage.tsx +111 -0
  208. package/src/hooks/useMergedRefs.tsx +48 -0
  209. package/src/hooks/useOnClickOutside.tsx +55 -0
  210. package/src/hooks/usePrevious.tsx +44 -0
  211. package/src/hooks/useThrottle.tsx +78 -0
  212. package/src/hooks/useTimeout.tsx +51 -0
  213. package/src/index.ts +23 -0
  214. package/src/lib/utilities.ts +4 -4
  215. package/src/motion/tokens.ts +4 -4
  216. package/src/stories/DesignPrinciples.mdx +48 -0
  217. package/src/stories/GettingStarted.mdx +92 -0
  218. package/src/stories/Welcome.mdx +44 -0
@@ -33,13 +33,14 @@ export interface ProgressProps extends Omit<React.ComponentPropsWithRef<typeof B
33
33
  *
34
34
  * @see {@link https://base-ui.com/react/components/progress | Base UI Documentation}
35
35
  */
36
- function Progress(props: Readonly<Progress.Props>): React.ReactElement {
36
+ const Progress = React.forwardRef<HTMLDivElement, Progress.Props>(function Progress(props, forwardedRef) {
37
37
  const {className, render, value = 0, ...otherProps} = props;
38
38
 
39
39
  return (
40
40
  <BaseProgress.Root
41
41
  value={value}
42
42
  {...otherProps}
43
+ ref={forwardedRef}
43
44
  render={useRender({
44
45
  defaultTagName: "div",
45
46
  render: render as never,
@@ -49,7 +50,7 @@ function Progress(props: Readonly<Progress.Props>): React.ReactElement {
49
50
  </BaseProgress.Track>
50
51
  </BaseProgress.Root>
51
52
  );
52
- }
53
+ });
53
54
  Progress.displayName = "Progress";
54
55
 
55
56
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
@@ -10,9 +10,6 @@ import * as React from "react";
10
10
  import {cn} from "@/lib/utilities";
11
11
  import styles from "./radio-group.module.css";
12
12
 
13
- type RadioGroupProps = React.ComponentPropsWithRef<typeof BaseRadioGroup>;
14
- type RadioGroupItemProps = React.ComponentPropsWithRef<typeof Radio.Root>;
15
-
16
13
  /**
17
14
  * Coordinates radio group state and selection behavior.
18
15
  *
@@ -84,13 +81,13 @@ const RadioGroupItem = React.forwardRef<React.ComponentRef<typeof Radio.Root>, R
84
81
 
85
82
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
86
83
  namespace RadioGroup {
87
- export type Props = RadioGroupProps;
84
+ export type Props = React.ComponentPropsWithRef<typeof BaseRadioGroup>;
88
85
  export type State = BaseRadioGroup.State;
89
86
  }
90
87
 
91
88
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
92
89
  namespace RadioGroupItem {
93
- export type Props = RadioGroupItemProps;
90
+ export type Props = React.ComponentPropsWithRef<typeof Radio.Root>;
94
91
  export type State = Radio.Root.State;
95
92
  }
96
93
 
@@ -4,14 +4,14 @@
4
4
 
5
5
  import {GripVertical} from "lucide-react";
6
6
  import * as React from "react";
7
- import type {ImperativePanelGroupHandle, ImperativePanelHandle} from "react-resizable-panels";
7
+ import type {ImperativePanelGroupHandle} from "react-resizable-panels";
8
8
  import * as ResizablePrimitive from "react-resizable-panels";
9
9
 
10
10
  import {cn} from "@/lib/utilities";
11
11
 
12
12
  import styles from "./resizable.module.css";
13
13
 
14
- export type {ImperativePanelGroupHandle, ImperativePanelHandle};
14
+ export type {ImperativePanelGroupHandle, ImperativePanelHandle} from "react-resizable-panels";
15
15
 
16
16
  /**
17
17
  * Props for the {@link ResizablePanelGroup} component.
@@ -43,15 +43,10 @@ const ignoreAnimationError = (): null => null;
43
43
  *
44
44
  * @see {@link ScratcherProps} for available props
45
45
  */
46
- export const Scratcher: React.FC<ScratcherProps> = ({
47
- width,
48
- height,
49
- minScratchPercentage = 50,
50
- onComplete,
51
- children,
52
- className,
53
- gradientColors = defaultGradientColors,
54
- }): React.JSX.Element => {
46
+ export const Scratcher = React.forwardRef<HTMLDivElement, ScratcherProps>(function Scratcher(
47
+ {width, height, minScratchPercentage = 50, onComplete, children, className, gradientColors = defaultGradientColors},
48
+ forwardedRef,
49
+ ) {
55
50
  const canvasRef = useRef<HTMLCanvasElement>(null);
56
51
  const [isScratching, setIsScratching] = useState(false);
57
52
  const [isComplete, setIsComplete] = useState(false);
@@ -173,6 +168,7 @@ export const Scratcher: React.FC<ScratcherProps> = ({
173
168
 
174
169
  return (
175
170
  <motion.div
171
+ ref={forwardedRef}
176
172
  className={cn(styles.root, className)}
177
173
  style={{
178
174
  width,
@@ -192,6 +188,6 @@ export const Scratcher: React.FC<ScratcherProps> = ({
192
188
  {children}
193
189
  </motion.div>
194
190
  );
195
- };
191
+ });
196
192
 
197
193
  Scratcher.displayName = "Scratcher";
@@ -8,9 +8,6 @@ import * as React from "react";
8
8
  import {cn} from "@/lib/utilities";
9
9
  import styles from "./scroll-area.module.css";
10
10
 
11
- type ScrollAreaProps = React.ComponentPropsWithRef<typeof BaseScrollArea.Root>;
12
- type ScrollBarProps = React.ComponentPropsWithRef<typeof BaseScrollArea.Scrollbar>;
13
-
14
11
  /**
15
12
  * Coordinates scroll area layout and scrolling behavior.
16
13
  *
@@ -88,13 +85,13 @@ function ScrollBar(props: Readonly<ScrollBar.Props>): React.ReactElement {
88
85
 
89
86
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
90
87
  namespace ScrollArea {
91
- export type Props = ScrollAreaProps;
88
+ export type Props = React.ComponentPropsWithRef<typeof BaseScrollArea.Root>;
92
89
  export type State = BaseScrollArea.Root.State;
93
90
  }
94
91
 
95
92
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
96
93
  namespace ScrollBar {
97
- export type Props = ScrollBarProps;
94
+ export type Props = React.ComponentPropsWithRef<typeof BaseScrollArea.Scrollbar>;
98
95
  export type State = BaseScrollArea.Scrollbar.State;
99
96
  }
100
97
 
@@ -36,13 +36,14 @@ export interface SeparatorProps extends Omit<React.ComponentPropsWithRef<typeof
36
36
  *
37
37
  * @see {@link https://base-ui.com/react/components/separator | Base UI Documentation}
38
38
  */
39
- function Separator(props: Readonly<Separator.Props>): React.ReactElement {
40
- const {className, decorative: _decorative = true, orientation = "horizontal", render, ...otherProps} = props;
39
+ const Separator = React.forwardRef<HTMLDivElement, Separator.Props>(function Separator(props, forwardedRef) {
40
+ const {className, orientation = "horizontal", render, ...otherProps} = props;
41
41
 
42
42
  return (
43
43
  <BaseSeparator
44
44
  orientation={orientation}
45
45
  {...otherProps}
46
+ ref={forwardedRef}
46
47
  render={useRender({
47
48
  defaultTagName: "div",
48
49
  render: render as never,
@@ -55,7 +56,7 @@ function Separator(props: Readonly<Separator.Props>): React.ReactElement {
55
56
  })}
56
57
  />
57
58
  );
58
- }
59
+ });
59
60
  Separator.displayName = "Separator";
60
61
 
61
62
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
@@ -171,6 +171,7 @@ const SheetClose = BaseDialog.Close;
171
171
  */
172
172
  const SheetTrigger = React.forwardRef<HTMLButtonElement, SheetTrigger.Props>(
173
173
  (props: Readonly<SheetTrigger.Props>, ref): React.ReactElement => {
174
+ // eslint-disable-next-line sonarjs/deprecation -- backward-compatible asChild API
174
175
  const {asChild = false, children, className, render, ...otherProps} = props;
175
176
  const renderProp = asChild && React.isValidElement(children) ? children : render;
176
177
 
@@ -277,6 +278,7 @@ const SheetContent = React.forwardRef<React.ComponentRef<typeof BaseDialog.Popup
277
278
  * @see {@link https://base-ui.com/react/components/dialog | Base UI Documentation}
278
279
  */
279
280
  function SheetHeader(props: Readonly<SheetHeader.Props>): React.ReactElement {
281
+ // eslint-disable-next-line sonarjs/deprecation -- backward-compatible asChild API
280
282
  const {asChild = false, children, className, render, ...otherProps} = props;
281
283
  const renderProp = asChild && React.isValidElement(children) ? children : render;
282
284
 
@@ -305,6 +307,7 @@ function SheetHeader(props: Readonly<SheetHeader.Props>): React.ReactElement {
305
307
  * @see {@link https://base-ui.com/react/components/dialog | Base UI Documentation}
306
308
  */
307
309
  function SheetFooter(props: Readonly<SheetFooter.Props>): React.ReactElement {
310
+ // eslint-disable-next-line sonarjs/deprecation -- backward-compatible asChild API
308
311
  const {asChild = false, children, className, render, ...otherProps} = props;
309
312
  const renderProp = asChild && React.isValidElement(children) ? children : render;
310
313
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  "use client";
2
3
 
3
4
  import {PanelLeft} from "lucide-react";
@@ -384,7 +384,7 @@ function updateToast(toastId: ToastIdentifier, options: ToastUpdateOptions): str
384
384
  const normalizedToastId = String(toastId);
385
385
  const existingRecord = toastRecords.get(normalizedToastId);
386
386
  const mergedOptions: ToastOptions = {
387
- ...(existingRecord?.options ?? {}),
387
+ ...existingRecord?.options,
388
388
  ...options,
389
389
  };
390
390
  const variant = options.variant ?? existingRecord?.snapshot.variant ?? "default";
@@ -594,16 +594,23 @@ function ToastViewportContent(): React.JSX.Element {
594
594
  *
595
595
  * @see {@link https://base-ui.com/react/components/toast | Base UI Toast Docs}
596
596
  */
597
- function Toaster({
598
- className,
599
- closeButton = true,
600
- containerAriaLabel = DEFAULT_VIEWPORT_ARIA_LABEL,
601
- duration = DEFAULT_TOAST_DURATION,
602
- position = "bottom-right",
603
- style,
604
- toastOptions,
605
- visibleToasts = DEFAULT_TOAST_LIMIT,
606
- }: Readonly<ToasterProps>): React.JSX.Element {
597
+ /**
598
+ * Toaster is the root viewport container for displaying toast notifications.
599
+ * It should be rendered once at the app root level.
600
+ */
601
+ const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>(function Toaster(
602
+ {
603
+ className,
604
+ closeButton = true,
605
+ containerAriaLabel = DEFAULT_VIEWPORT_ARIA_LABEL,
606
+ duration = DEFAULT_TOAST_DURATION,
607
+ position = "bottom-right",
608
+ style,
609
+ toastOptions,
610
+ visibleToasts = DEFAULT_TOAST_LIMIT,
611
+ },
612
+ forwardedRef,
613
+ ) {
607
614
  const toasterId = React.useId();
608
615
 
609
616
  React.useEffect(() => {
@@ -624,6 +631,7 @@ function Toaster({
624
631
  toastManager={toastManager}>
625
632
  <Toast.Portal>
626
633
  <Toast.Viewport
634
+ ref={forwardedRef}
627
635
  aria-label={containerAriaLabel}
628
636
  className={cn(styles.viewport, positionStyles[position], className)}
629
637
  style={style}>
@@ -632,7 +640,7 @@ function Toaster({
632
640
  </Toast.Portal>
633
641
  </Toast.Provider>
634
642
  );
635
- }
643
+ });
636
644
 
637
645
  Toaster.displayName = "Toaster";
638
646
 
@@ -63,12 +63,13 @@ export interface ToggleGroupItemProps extends Omit<ToggleProps, "pressed" | "def
63
63
  *
64
64
  * @see {@link https://base-ui.com/react/components/toggle-group | Base UI Toggle Group Docs}
65
65
  */
66
- function ToggleGroup(props: Readonly<ToggleGroup.Props>): React.ReactElement {
66
+ const ToggleGroup = React.forwardRef<HTMLDivElement, ToggleGroup.Props>(function ToggleGroup(props, forwardedRef) {
67
67
  const {className, children, render, size, variant, ...otherProps} = props;
68
68
 
69
69
  return (
70
70
  <BaseToggleGroup
71
71
  {...otherProps}
72
+ ref={forwardedRef}
72
73
  render={useRender({
73
74
  defaultTagName: "div",
74
75
  render: render as never,
@@ -77,7 +78,7 @@ function ToggleGroup(props: Readonly<ToggleGroup.Props>): React.ReactElement {
77
78
  <ToggleGroupContext.Provider value={{variant, size}}>{children}</ToggleGroupContext.Provider>
78
79
  </BaseToggleGroup>
79
80
  );
80
- }
81
+ });
81
82
 
82
83
  /**
83
84
  * Renders an individual toggle item within a toggle group.
@@ -96,12 +97,13 @@ function ToggleGroup(props: Readonly<ToggleGroup.Props>): React.ReactElement {
96
97
  *
97
98
  * @see {@link https://base-ui.com/react/components/toggle-group | Base UI Toggle Group Docs}
98
99
  */
99
- function ToggleGroupItem(props: Readonly<ToggleGroupItem.Props>): React.ReactElement {
100
+ const ToggleGroupItem = React.forwardRef<HTMLButtonElement, ToggleGroupItem.Props>(function ToggleGroupItem(props, forwardedRef) {
100
101
  const {className, size, variant, ...otherProps} = props;
101
102
  const context = React.useContext(ToggleGroupContext);
102
103
 
103
104
  return (
104
105
  <Toggle
106
+ ref={forwardedRef}
105
107
  className={cn(
106
108
  toggleVariants({
107
109
  variant: variant ?? context.variant,
@@ -114,7 +116,7 @@ function ToggleGroupItem(props: Readonly<ToggleGroupItem.Props>): React.ReactEle
114
116
  {...otherProps}
115
117
  />
116
118
  );
117
- }
119
+ });
118
120
 
119
121
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
120
122
  namespace ToggleGroup {
@@ -8,12 +8,6 @@ import * as React from "react";
8
8
  import {cn} from "@/lib/utilities";
9
9
  import styles from "./toolbar.module.css";
10
10
 
11
- type ToolbarProps = React.ComponentPropsWithRef<typeof BaseToolbar.Root>;
12
- type ToolbarButtonProps = React.ComponentPropsWithRef<typeof BaseToolbar.Button>;
13
- type ToolbarGroupProps = React.ComponentPropsWithRef<typeof BaseToolbar.Group>;
14
- type ToolbarSeparatorProps = React.ComponentPropsWithRef<typeof BaseToolbar.Separator>;
15
- type ToolbarLinkProps = React.ComponentPropsWithRef<typeof BaseToolbar.Link>;
16
-
17
11
  /**
18
12
  * Arranges related actions into a keyboard-accessible toolbar.
19
13
  *
@@ -32,12 +26,13 @@ type ToolbarLinkProps = React.ComponentPropsWithRef<typeof BaseToolbar.Link>;
32
26
  *
33
27
  * @see {@link https://base-ui.com/react/components/toolbar | Base UI Toolbar Docs}
34
28
  */
35
- function Toolbar(props: Readonly<Toolbar.Props>): React.ReactElement {
29
+ const Toolbar = React.forwardRef<HTMLDivElement, Toolbar.Props>(function Toolbar(props, forwardedRef) {
36
30
  const {className, children, render, ...otherProps} = props;
37
31
 
38
32
  return (
39
33
  <BaseToolbar.Root
40
34
  {...otherProps}
35
+ ref={forwardedRef}
41
36
  render={useRender({
42
37
  defaultTagName: "div",
43
38
  render: render as never,
@@ -46,7 +41,7 @@ function Toolbar(props: Readonly<Toolbar.Props>): React.ReactElement {
46
41
  {children}
47
42
  </BaseToolbar.Root>
48
43
  );
49
- }
44
+ });
50
45
 
51
46
  /**
52
47
  * Renders an interactive button within a toolbar.
@@ -63,12 +58,13 @@ function Toolbar(props: Readonly<Toolbar.Props>): React.ReactElement {
63
58
  *
64
59
  * @see {@link https://base-ui.com/react/components/toolbar | Base UI Toolbar Docs}
65
60
  */
66
- function ToolbarButton(props: Readonly<ToolbarButton.Props>): React.ReactElement {
61
+ const ToolbarButton = React.forwardRef<HTMLButtonElement, ToolbarButton.Props>(function ToolbarButton(props, forwardedRef) {
67
62
  const {className, children, render, ...otherProps} = props;
68
63
 
69
64
  return (
70
65
  <BaseToolbar.Button
71
66
  {...otherProps}
67
+ ref={forwardedRef}
72
68
  render={useRender({
73
69
  defaultTagName: "button",
74
70
  render: render as never,
@@ -77,7 +73,7 @@ function ToolbarButton(props: Readonly<ToolbarButton.Props>): React.ReactElement
77
73
  {children}
78
74
  </BaseToolbar.Button>
79
75
  );
80
- }
76
+ });
81
77
 
82
78
  /**
83
79
  * Groups related toolbar controls into a single visual cluster.
@@ -96,12 +92,13 @@ function ToolbarButton(props: Readonly<ToolbarButton.Props>): React.ReactElement
96
92
  *
97
93
  * @see {@link https://base-ui.com/react/components/toolbar | Base UI Toolbar Docs}
98
94
  */
99
- function ToolbarGroup(props: Readonly<ToolbarGroup.Props>): React.ReactElement {
95
+ const ToolbarGroup = React.forwardRef<HTMLDivElement, ToolbarGroup.Props>(function ToolbarGroup(props, forwardedRef) {
100
96
  const {className, children, render, ...otherProps} = props;
101
97
 
102
98
  return (
103
99
  <BaseToolbar.Group
104
100
  {...otherProps}
101
+ ref={forwardedRef}
105
102
  render={useRender({
106
103
  defaultTagName: "div",
107
104
  render: render as never,
@@ -110,7 +107,7 @@ function ToolbarGroup(props: Readonly<ToolbarGroup.Props>): React.ReactElement {
110
107
  {children}
111
108
  </BaseToolbar.Group>
112
109
  );
113
- }
110
+ });
114
111
 
115
112
  /**
116
113
  * Renders a visual separator between toolbar items or groups.
@@ -126,12 +123,13 @@ function ToolbarGroup(props: Readonly<ToolbarGroup.Props>): React.ReactElement {
126
123
  *
127
124
  * @see {@link https://base-ui.com/react/components/toolbar | Base UI Toolbar Docs}
128
125
  */
129
- function ToolbarSeparator(props: Readonly<ToolbarSeparator.Props>): React.ReactElement {
126
+ const ToolbarSeparator = React.forwardRef<HTMLDivElement, ToolbarSeparator.Props>(function ToolbarSeparator(props, forwardedRef) {
130
127
  const {className, render, ...otherProps} = props;
131
128
 
132
129
  return (
133
130
  <BaseToolbar.Separator
134
131
  {...otherProps}
132
+ ref={forwardedRef}
135
133
  render={useRender({
136
134
  defaultTagName: "div",
137
135
  render: render as never,
@@ -139,7 +137,7 @@ function ToolbarSeparator(props: Readonly<ToolbarSeparator.Props>): React.ReactE
139
137
  })}
140
138
  />
141
139
  );
142
- }
140
+ });
143
141
 
144
142
  /**
145
143
  * Renders a link that visually matches toolbar buttons.
@@ -155,12 +153,13 @@ function ToolbarSeparator(props: Readonly<ToolbarSeparator.Props>): React.ReactE
155
153
  *
156
154
  * @see {@link https://base-ui.com/react/components/toolbar | Base UI Toolbar Docs}
157
155
  */
158
- function ToolbarLink(props: Readonly<ToolbarLink.Props>): React.ReactElement {
156
+ const ToolbarLink = React.forwardRef<HTMLAnchorElement, ToolbarLink.Props>(function ToolbarLink(props, forwardedRef) {
159
157
  const {className, children, render, ...otherProps} = props;
160
158
 
161
159
  return (
162
160
  <BaseToolbar.Link
163
161
  {...otherProps}
162
+ ref={forwardedRef}
164
163
  render={useRender({
165
164
  defaultTagName: "a",
166
165
  render: render as never,
@@ -169,35 +168,35 @@ function ToolbarLink(props: Readonly<ToolbarLink.Props>): React.ReactElement {
169
168
  {children}
170
169
  </BaseToolbar.Link>
171
170
  );
172
- }
171
+ });
173
172
 
174
173
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
175
174
  namespace Toolbar {
176
- export type Props = ToolbarProps;
175
+ export type Props = React.ComponentPropsWithRef<typeof BaseToolbar.Root>;
177
176
  export type State = BaseToolbar.Root.State;
178
177
  }
179
178
 
180
179
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
181
180
  namespace ToolbarButton {
182
- export type Props = ToolbarButtonProps;
181
+ export type Props = React.ComponentPropsWithRef<typeof BaseToolbar.Button>;
183
182
  export type State = BaseToolbar.Button.State;
184
183
  }
185
184
 
186
185
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
187
186
  namespace ToolbarGroup {
188
- export type Props = ToolbarGroupProps;
187
+ export type Props = React.ComponentPropsWithRef<typeof BaseToolbar.Group>;
189
188
  export type State = BaseToolbar.Group.State;
190
189
  }
191
190
 
192
191
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
193
192
  namespace ToolbarSeparator {
194
- export type Props = ToolbarSeparatorProps;
193
+ export type Props = React.ComponentPropsWithRef<typeof BaseToolbar.Separator>;
195
194
  export type State = BaseToolbar.Separator.State;
196
195
  }
197
196
 
198
197
  // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API
199
198
  namespace ToolbarLink {
200
- export type Props = ToolbarLinkProps;
199
+ export type Props = React.ComponentPropsWithRef<typeof BaseToolbar.Link>;
201
200
  export type State = BaseToolbar.Link.State;
202
201
  }
203
202
 
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /**
6
+ * Options for the `useClipboard` hook.
7
+ */
8
+ export interface UseClipboardOptions {
9
+ /**
10
+ * The duration in milliseconds after which the `copied` state resets to `false`.
11
+ *
12
+ * @defaultValue 2000
13
+ */
14
+ timeout?: number;
15
+ }
16
+
17
+ /**
18
+ * Return type for the `useClipboard` hook.
19
+ */
20
+ export interface UseClipboardReturn {
21
+ /**
22
+ * Whether the text was successfully copied to the clipboard.
23
+ */
24
+ copied: boolean;
25
+
26
+ /**
27
+ * Copies the provided text to the clipboard.
28
+ *
29
+ * @param text - The text to copy.
30
+ * @returns A promise that resolves when the copy operation completes.
31
+ */
32
+ copy: (text: string) => Promise<void>;
33
+
34
+ /**
35
+ * The error that occurred during the copy operation, or `null` if no error occurred.
36
+ */
37
+ error: Error | null;
38
+ }
39
+
40
+ /**
41
+ * Copies text to the clipboard with success/error state management.
42
+ *
43
+ * @remarks
44
+ * This hook provides a simple interface for copying text to the clipboard using
45
+ * the Clipboard API. It manages the `copied` state that automatically resets
46
+ * after a configurable timeout, and handles errors gracefully when the Clipboard
47
+ * API is unavailable or the operation fails.
48
+ *
49
+ * The hook is SSR-safe and will handle the case where `navigator.clipboard` is
50
+ * not available (e.g., in non-secure contexts or older browsers).
51
+ *
52
+ * @param options - Configuration options for the hook.
53
+ * @returns An object containing the copied state, copy function, and any error.
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * function CopyButton({textToCopy}: {textToCopy: string}) {
58
+ * const {copied, copy, error} = useClipboard({timeout: 3000});
59
+ *
60
+ * return (
61
+ * <div>
62
+ * <button onClick={() => copy(textToCopy)}>
63
+ * {copied ? "Copied!" : "Copy to clipboard"}
64
+ * </button>
65
+ * {error && <span>Failed to copy: {error.message}</span>}
66
+ * </div>
67
+ * );
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * function ShareLink({url}: {url: string}) {
74
+ * const {copied, copy} = useClipboard();
75
+ *
76
+ * return (
77
+ * <button onClick={() => copy(url)} disabled={copied}>
78
+ * {copied ? "✓ Copied" : "Share link"}
79
+ * </button>
80
+ * );
81
+ * }
82
+ * ```
83
+ */
84
+ export function useClipboard(options: UseClipboardOptions = {}): UseClipboardReturn {
85
+ const {timeout = 2000} = options;
86
+
87
+ const [copied, setCopied] = React.useState(false);
88
+ const [error, setError] = React.useState<Error | null>(null);
89
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
90
+
91
+ const copy = React.useCallback(
92
+ async (text: string): Promise<void> => {
93
+ // Clear any existing timeout
94
+ if (timeoutRef.current !== null) {
95
+ clearTimeout(timeoutRef.current);
96
+ timeoutRef.current = null;
97
+ }
98
+
99
+ // Reset error state
100
+ setError(null);
101
+
102
+ try {
103
+ // Check if Clipboard API is available
104
+ if (typeof globalThis.navigator === "undefined" || !globalThis.navigator.clipboard) {
105
+ throw new Error("Clipboard API is not available");
106
+ }
107
+
108
+ await globalThis.navigator.clipboard.writeText(text);
109
+ setCopied(true);
110
+
111
+ // Reset copied state after timeout
112
+ timeoutRef.current = setTimeout(() => {
113
+ setCopied(false);
114
+ timeoutRef.current = null;
115
+ }, timeout);
116
+ } catch (err) {
117
+ const errorMessage = err instanceof Error ? err : new Error("Failed to copy to clipboard");
118
+
119
+ setError(errorMessage);
120
+ setCopied(false);
121
+ console.error("Copy to clipboard failed:", errorMessage);
122
+ }
123
+ },
124
+ [timeout],
125
+ );
126
+
127
+ // Cleanup timeout on unmount
128
+ React.useEffect(() => {
129
+ return () => {
130
+ if (timeoutRef.current !== null) {
131
+ clearTimeout(timeoutRef.current);
132
+ }
133
+ };
134
+ }, []);
135
+
136
+ return {copied, copy, error};
137
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /**
6
+ * Options for configuring controllable state behavior.
7
+ *
8
+ * @typeParam T - The type of the state value.
9
+ */
10
+ export interface UseControllableStateOptions<T> {
11
+ /**
12
+ * The controlled value from props. When provided, the component operates in controlled mode.
13
+ */
14
+ controlled?: T;
15
+ /**
16
+ * The default value used when the component is uncontrolled.
17
+ */
18
+ defaultValue: T;
19
+ /**
20
+ * Callback fired when the internal state changes in controlled mode.
21
+ *
22
+ * @param value - The new state value.
23
+ */
24
+ onChange?: (value: T) => void;
25
+ }
26
+
27
+ /**
28
+ * Manages state that can be either controlled or uncontrolled.
29
+ *
30
+ * @remarks
31
+ * This hook enables components to support both controlled and uncontrolled patterns
32
+ * seamlessly. When a `controlled` value is provided, it takes precedence; otherwise,
33
+ * the hook manages internal state initialized with `defaultValue`.
34
+ *
35
+ * @typeParam T - The type of the state value.
36
+ * @param options - Configuration object for controllable state.
37
+ * @returns A tuple containing the current state value and a setter function.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * function CustomInput({value, defaultValue = "", onChange}) {
42
+ * const [internalValue, setValue] = useControllableState({
43
+ * controlled: value,
44
+ * defaultValue,
45
+ * onChange,
46
+ * });
47
+ *
48
+ * return (
49
+ * <input
50
+ * type="text"
51
+ * value={internalValue}
52
+ * onChange={(e) => setValue(e.target.value)}
53
+ * />
54
+ * );
55
+ * }
56
+ * ```
57
+ */
58
+ export function useControllableState<T>(options: UseControllableStateOptions<T>): [T, (value: T | ((prev: T) => T)) => void] {
59
+ const {controlled, defaultValue, onChange} = options;
60
+ const [uncontrolledState, setUncontrolledState] = React.useState<T>(defaultValue);
61
+ const isControlled = controlled !== undefined;
62
+ const value = isControlled ? controlled : uncontrolledState;
63
+
64
+ const setValue = React.useCallback(
65
+ (nextValue: T | ((prev: T) => T)) => {
66
+ if (!isControlled) {
67
+ setUncontrolledState((currentValue) => {
68
+ const resolvedValue = typeof nextValue === "function" ? (nextValue as (prev: T) => T)(currentValue) : nextValue;
69
+ onChange?.(resolvedValue);
70
+ return resolvedValue;
71
+ });
72
+ } else {
73
+ const resolvedValue = typeof nextValue === "function" ? (nextValue as (prev: T) => T)(controlled as T) : nextValue;
74
+ onChange?.(resolvedValue);
75
+ }
76
+ },
77
+ [isControlled, onChange, controlled],
78
+ );
79
+
80
+ return [value, setValue];
81
+ }