@blenx-dev/core 0.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 (172) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +21 -0
  3. package/package.json +49 -0
  4. package/src/DataTable/data-table-column-toggle.tsx +73 -0
  5. package/src/DataTable/data-table-empty.tsx +27 -0
  6. package/src/DataTable/data-table-error.tsx +25 -0
  7. package/src/DataTable/data-table-infinite-loader.tsx +73 -0
  8. package/src/DataTable/data-table-loading.tsx +67 -0
  9. package/src/DataTable/data-table-pagination.tsx +80 -0
  10. package/src/DataTable/data-table-toolbar.tsx +62 -0
  11. package/src/DataTable/data-table.css.ts +420 -0
  12. package/src/DataTable/data-table.tsx +507 -0
  13. package/src/DataTable/index.ts +24 -0
  14. package/src/DataTable/types.ts +169 -0
  15. package/src/DataTable/use-infinite-scroll.ts +67 -0
  16. package/src/components/Accordion/accordion.css.ts +84 -0
  17. package/src/components/Accordion/accordion.tsx +87 -0
  18. package/src/components/Accordion/index.ts +8 -0
  19. package/src/components/Alert/alert.css.ts +29 -0
  20. package/src/components/Alert/alert.tsx +40 -0
  21. package/src/components/Alert/index.ts +1 -0
  22. package/src/components/AlertDialog/alert-dialog.css.ts +62 -0
  23. package/src/components/AlertDialog/alert-dialog.tsx +199 -0
  24. package/src/components/AlertDialog/index.ts +1 -0
  25. package/src/components/AspectRatio/aspect-ratio.css.ts +7 -0
  26. package/src/components/AspectRatio/aspect-ratio.tsx +20 -0
  27. package/src/components/AspectRatio/index.ts +1 -0
  28. package/src/components/Autocomplete/autocomplete.css.ts +167 -0
  29. package/src/components/Autocomplete/autocomplete.tsx +226 -0
  30. package/src/components/Autocomplete/index.ts +1 -0
  31. package/src/components/Avatar/avatar.css.ts +65 -0
  32. package/src/components/Avatar/avatar.tsx +44 -0
  33. package/src/components/Avatar/index.ts +1 -0
  34. package/src/components/Badge/badge.css.ts +180 -0
  35. package/src/components/Badge/badge.tsx +47 -0
  36. package/src/components/Badge/index.ts +1 -0
  37. package/src/components/Box/box.css.ts +5 -0
  38. package/src/components/Box/box.tsx +21 -0
  39. package/src/components/Box/index.ts +1 -0
  40. package/src/components/Breadcrumbs/breadcrumbs.css.ts +72 -0
  41. package/src/components/Breadcrumbs/breadcrumbs.tsx +79 -0
  42. package/src/components/Breadcrumbs/index.ts +9 -0
  43. package/src/components/Button/button.css.ts +200 -0
  44. package/src/components/Button/button.tsx +55 -0
  45. package/src/components/Button/index.ts +1 -0
  46. package/src/components/Calendar/calendar.css.ts +187 -0
  47. package/src/components/Calendar/calendar.tsx +143 -0
  48. package/src/components/Calendar/index.ts +1 -0
  49. package/src/components/Card/card.tsx +32 -0
  50. package/src/components/Card/index.ts +1 -0
  51. package/src/components/Checkbox/checkbox.css.ts +76 -0
  52. package/src/components/Checkbox/checkbox.tsx +94 -0
  53. package/src/components/Checkbox/index.ts +1 -0
  54. package/src/components/CloseButton/close-button.css.ts +11 -0
  55. package/src/components/CloseButton/close-button.tsx +15 -0
  56. package/src/components/CloseButton/index.ts +2 -0
  57. package/src/components/ColorPicker/color-picker.tsx +123 -0
  58. package/src/components/ColorPicker/index.ts +1 -0
  59. package/src/components/ColorSwatch/color-swatch.tsx +21 -0
  60. package/src/components/ColorSwatch/index.ts +1 -0
  61. package/src/components/Combobox/combobox.css.ts +333 -0
  62. package/src/components/Combobox/combobox.tsx +350 -0
  63. package/src/components/Combobox/index.ts +1 -0
  64. package/src/components/Command/command.css.ts +130 -0
  65. package/src/components/Command/command.tsx +413 -0
  66. package/src/components/Command/index.ts +7 -0
  67. package/src/components/Container/container.css.ts +41 -0
  68. package/src/components/Container/container.tsx +25 -0
  69. package/src/components/Container/index.ts +1 -0
  70. package/src/components/CopyButton/copy-button.css.ts +11 -0
  71. package/src/components/CopyButton/copy-button.tsx +45 -0
  72. package/src/components/CopyButton/index.ts +2 -0
  73. package/src/components/DatePicker/date-picker.tsx +75 -0
  74. package/src/components/DatePicker/index.ts +1 -0
  75. package/src/components/Dialog/dialog.css.ts +57 -0
  76. package/src/components/Dialog/dialog.tsx +181 -0
  77. package/src/components/Dialog/index.ts +1 -0
  78. package/src/components/Drawer/drawer.css.ts +404 -0
  79. package/src/components/Drawer/drawer.tsx +573 -0
  80. package/src/components/Drawer/index.ts +1 -0
  81. package/src/components/Field/field.css.ts +35 -0
  82. package/src/components/Field/field.tsx +101 -0
  83. package/src/components/Field/index.ts +1 -0
  84. package/src/components/Grid/grid.css.ts +12 -0
  85. package/src/components/Grid/grid.tsx +32 -0
  86. package/src/components/Grid/index.ts +1 -0
  87. package/src/components/Icon/icon.css.ts +10 -0
  88. package/src/components/Icon/icon.tsx +15 -0
  89. package/src/components/Icon/index.ts +1 -0
  90. package/src/components/IconButton/icon-button.css.ts +6 -0
  91. package/src/components/IconButton/icon-button.tsx +11 -0
  92. package/src/components/IconButton/index.ts +2 -0
  93. package/src/components/Input/index.ts +1 -0
  94. package/src/components/Input/input.css.ts +72 -0
  95. package/src/components/Input/input.tsx +50 -0
  96. package/src/components/InputGroup/index.ts +1 -0
  97. package/src/components/InputGroup/input-group.css.ts +156 -0
  98. package/src/components/InputGroup/input-group.tsx +133 -0
  99. package/src/components/Menu/index.ts +1 -0
  100. package/src/components/Menu/menu.css.ts +121 -0
  101. package/src/components/Menu/menu.tsx +115 -0
  102. package/src/components/OTPField/index.ts +1 -0
  103. package/src/components/OTPField/otp-field.css.ts +54 -0
  104. package/src/components/OTPField/otp-field.tsx +46 -0
  105. package/src/components/Popover/index.ts +1 -0
  106. package/src/components/Popover/popover.css.ts +81 -0
  107. package/src/components/Popover/popover.tsx +113 -0
  108. package/src/components/Progress/index.ts +7 -0
  109. package/src/components/Progress/progress.css.ts +37 -0
  110. package/src/components/Progress/progress.tsx +62 -0
  111. package/src/components/Radio/index.ts +1 -0
  112. package/src/components/Radio/radio.css.ts +72 -0
  113. package/src/components/Radio/radio.tsx +49 -0
  114. package/src/components/ScrollArea/index.ts +1 -0
  115. package/src/components/ScrollArea/scroll-area.css.ts +79 -0
  116. package/src/components/ScrollArea/scroll-area.tsx +96 -0
  117. package/src/components/SegmentedControl/index.ts +1 -0
  118. package/src/components/SegmentedControl/segmented-control.tsx +42 -0
  119. package/src/components/Select/index.ts +1 -0
  120. package/src/components/Select/select.css.ts +182 -0
  121. package/src/components/Select/select.tsx +165 -0
  122. package/src/components/Separator/index.ts +1 -0
  123. package/src/components/Separator/separator.css.ts +59 -0
  124. package/src/components/Separator/separator.tsx +34 -0
  125. package/src/components/Sheet/index.ts +1 -0
  126. package/src/components/Sheet/sheet.css.ts +184 -0
  127. package/src/components/Sheet/sheet.tsx +215 -0
  128. package/src/components/Slider/index.ts +1 -0
  129. package/src/components/Slider/slider.css.ts +81 -0
  130. package/src/components/Slider/slider.tsx +100 -0
  131. package/src/components/Spinner/index.ts +1 -0
  132. package/src/components/Spinner/spinner.css.ts +17 -0
  133. package/src/components/Spinner/spinner.tsx +15 -0
  134. package/src/components/Splitter/index.ts +1 -0
  135. package/src/components/Splitter/splitter.css.ts +69 -0
  136. package/src/components/Splitter/splitter.tsx +521 -0
  137. package/src/components/Stack/index.ts +1 -0
  138. package/src/components/Stack/stack.css.ts +42 -0
  139. package/src/components/Stack/stack.tsx +32 -0
  140. package/src/components/Surface/index.ts +1 -0
  141. package/src/components/Surface/surface.css.ts +40 -0
  142. package/src/components/Surface/surface.tsx +19 -0
  143. package/src/components/Switch/index.ts +1 -0
  144. package/src/components/Switch/switch.css.ts +46 -0
  145. package/src/components/Switch/switch.tsx +25 -0
  146. package/src/components/Table/index.ts +2 -0
  147. package/src/components/Table/table.css.ts +71 -0
  148. package/src/components/Table/table.tsx +117 -0
  149. package/src/components/Tabs/index.ts +1 -0
  150. package/src/components/Tabs/tabs.css.ts +250 -0
  151. package/src/components/Tabs/tabs.tsx +119 -0
  152. package/src/components/Text/index.ts +2 -0
  153. package/src/components/Text/text.css.ts +118 -0
  154. package/src/components/Text/text.tsx +66 -0
  155. package/src/components/Textarea/index.ts +1 -0
  156. package/src/components/Textarea/textarea.css.ts +66 -0
  157. package/src/components/Textarea/textarea.tsx +48 -0
  158. package/src/components/Toggle/index.ts +1 -0
  159. package/src/components/Toggle/toggle.css.ts +91 -0
  160. package/src/components/Toggle/toggle.tsx +44 -0
  161. package/src/components/ToggleGroup/index.ts +1 -0
  162. package/src/components/ToggleGroup/toggle-group.css.ts +77 -0
  163. package/src/components/ToggleGroup/toggle-group.tsx +131 -0
  164. package/src/components/index.ts +54 -0
  165. package/src/index.ts +3 -0
  166. package/src/utils/drawer-styles.css.ts +85 -0
  167. package/src/utils/heights.ts +16 -0
  168. package/src/utils/sprinkles.css.ts +197 -0
  169. package/src/utils/sprinkles.tokens.ts +74 -0
  170. package/src/utils/types.ts +10 -0
  171. package/src/utils/ve-style.utils.ts +51 -0
  172. package/tsconfig.json +10 -0
@@ -0,0 +1,143 @@
1
+ "use client";
2
+
3
+ import { CaretLeftIcon, CaretRightIcon, CaretUpDownIcon } from "@phosphor-icons/react";
4
+ import clsx from "clsx";
5
+ import { useEffect, useRef } from "react";
6
+ import { DayPicker, type DayPickerProps } from "react-day-picker";
7
+ import { Button } from "../Button/button";
8
+ import {
9
+ root,
10
+ months,
11
+ monthCaption,
12
+ captionLabel,
13
+ nav,
14
+ buttonNav,
15
+ monthGrid,
16
+ weekday,
17
+ day,
18
+ dayButton,
19
+ dayButtonSelected,
20
+ dayButtonToday,
21
+ dayButtonDisabled,
22
+ dayButtonOutside,
23
+ footer,
24
+ dropdowns,
25
+ dropdown,
26
+ } from "./calendar.css";
27
+
28
+ function StyledDayButton({
29
+ day: _day,
30
+ modifiers,
31
+ className,
32
+ ...props
33
+ }: {
34
+ day: object;
35
+ modifiers: Record<string, boolean>;
36
+ } & React.ComponentPropsWithoutRef<"button">) {
37
+ const ref = useRef<HTMLButtonElement>(null);
38
+
39
+ useEffect(() => {
40
+ if (modifiers.focused) {
41
+ ref.current?.focus();
42
+ }
43
+ }, [modifiers.focused]);
44
+
45
+ const isSelected = modifiers.selected;
46
+ const isToday = modifiers.today;
47
+ const isDisabled = modifiers.disabled;
48
+ const isOutside = modifiers.outside;
49
+
50
+ const composed = clsx(
51
+ dayButton,
52
+ isSelected && dayButtonSelected,
53
+ isToday && dayButtonToday,
54
+ isDisabled && dayButtonDisabled,
55
+ isOutside && dayButtonOutside,
56
+ className,
57
+ );
58
+
59
+ return <button ref={ref} className={composed} {...props} />;
60
+ }
61
+
62
+ function Calendar({ className, components: userComponents, ...props }: DayPickerProps) {
63
+ const classNames = {
64
+ root: clsx(root, className),
65
+ months: months,
66
+ month: "",
67
+ month_caption: monthCaption,
68
+ caption_label: captionLabel,
69
+ nav: nav,
70
+ button_previous: buttonNav,
71
+ button_next: buttonNav,
72
+ chevron: "",
73
+ month_grid: monthGrid,
74
+ weekdays: "",
75
+ weekday: weekday,
76
+ weeks: "",
77
+ week: "",
78
+ day: day,
79
+ day_button: "",
80
+ footer: footer,
81
+ dropdowns: dropdowns,
82
+ dropdown: dropdown,
83
+ dropdown_root: "",
84
+ months_dropdown: "",
85
+ years_dropdown: "",
86
+ week_number: "",
87
+ week_number_header: "",
88
+ selected: "",
89
+ today: "",
90
+ disabled: "",
91
+ hidden: "",
92
+ outside: "",
93
+ focused: "",
94
+ range_start: "",
95
+ range_end: "",
96
+ range_middle: "",
97
+ weeks_before_enter: "",
98
+ weeks_before_exit: "",
99
+ weeks_after_enter: "",
100
+ weeks_after_exit: "",
101
+ caption_after_enter: "",
102
+ caption_after_exit: "",
103
+ caption_before_enter: "",
104
+ caption_before_exit: "",
105
+ };
106
+ const defaultComponents = {
107
+ Chevron: ({
108
+ orientation,
109
+ ...chevronProps
110
+ }: {
111
+ orientation?: "left" | "right" | "up" | "down";
112
+ }): React.ReactElement => {
113
+ if (orientation === "left") {
114
+ return (
115
+ <Button size="icon" variant="ghost">
116
+ <CaretLeftIcon {...chevronProps} aria-hidden="true" />
117
+ </Button>
118
+ );
119
+ }
120
+ if (orientation === "right") {
121
+ return (
122
+ <Button size="icon" variant="ghost">
123
+ <CaretRightIcon {...chevronProps} aria-hidden="true" />
124
+ </Button>
125
+ );
126
+ }
127
+ return (
128
+ <Button size="icon" variant="ghost">
129
+ <CaretUpDownIcon {...chevronProps} aria-hidden="true" />
130
+ </Button>
131
+ );
132
+ },
133
+ };
134
+ const mergedComponents = {
135
+ ...defaultComponents,
136
+ ...userComponents,
137
+ DayButton: StyledDayButton,
138
+ };
139
+
140
+ return <DayPicker classNames={classNames} components={mergedComponents} {...props} />;
141
+ }
142
+
143
+ export { Calendar };
@@ -0,0 +1 @@
1
+ export * from "./calendar";
@@ -0,0 +1,32 @@
1
+ import { Surface, type SurfaceProps } from "../Surface/surface";
2
+
3
+ import { HStack, type HStackProps } from "../Stack/stack";
4
+ import { Box, type BoxProps } from "../Box/box";
5
+ import { Text, type TextProps } from "../Text";
6
+
7
+ function Card(props: SurfaceProps) {
8
+ return <Surface p="md" withBorder variant="sunken" {...props} />;
9
+ }
10
+
11
+ function CardHeader(props: BoxProps) {
12
+ return <Box marginBottom={"md"} {...props} />;
13
+ }
14
+
15
+ function CardBody(props: BoxProps) {
16
+ return <Box {...props} />;
17
+ }
18
+
19
+ function CardFooter(props: HStackProps) {
20
+ return <HStack justify="end" gap="sm" {...props} />;
21
+ }
22
+
23
+ function CardTitle(props: TextProps) {
24
+ return <Text variant="h2" {...props} />;
25
+ }
26
+
27
+ function CardDescription(props: TextProps) {
28
+ return <Text variant="body2" color="secondary" {...props} />;
29
+ }
30
+
31
+ export type { SurfaceProps as CardProps, TextProps as CardTitleProps };
32
+ export { Card, CardDescription, CardTitle, CardFooter, CardBody, CardHeader };
@@ -0,0 +1 @@
1
+ export * from "./card";
@@ -0,0 +1,76 @@
1
+ import { style } from "@vanilla-extract/css";
2
+ import { semanticVars, tokenVars } from "@blenx-dev/theme/contract";
3
+
4
+ export const root = style({
5
+ position: "relative",
6
+ display: "inline-flex",
7
+ alignItems: "center",
8
+ justifyContent: "center",
9
+ flexShrink: 0,
10
+ width: 18,
11
+ height: 18,
12
+ borderRadius: tokenVars.borderRadius.sm,
13
+ borderWidth: tokenVars.borderWidth.thin,
14
+ borderStyle: "solid",
15
+ borderColor: semanticVars.border.default,
16
+ backgroundColor: semanticVars.background.default,
17
+ outline: "none",
18
+ transitionProperty: "box-shadow",
19
+ transitionDuration: "150ms",
20
+ selectors: {
21
+ "&:focus-visible": {
22
+ boxShadow: `0 0 0 2px ${semanticVars.focus.ring}`,
23
+ },
24
+ },
25
+ "@media": {
26
+ "screen and (min-width: 640px)": {
27
+ width: 16,
28
+ height: 16,
29
+ },
30
+ },
31
+ });
32
+
33
+ export const rootDisabled = style({
34
+ cursor: "not-allowed",
35
+ opacity: 0.64,
36
+ });
37
+
38
+ export const group = style({
39
+ display: "flex",
40
+ flexDirection: "column",
41
+ alignItems: "flex-start",
42
+ gap: tokenVars.spacing.sm,
43
+ });
44
+
45
+ export const indicator = style({
46
+ position: "absolute",
47
+ top: -1,
48
+ left: -1,
49
+ right: -1,
50
+ bottom: -1,
51
+ display: "flex",
52
+ alignItems: "center",
53
+ justifyContent: "center",
54
+ borderRadius: tokenVars.borderRadius.sm,
55
+ color: semanticVars.interactive.primaryFg,
56
+ });
57
+
58
+ export const indicatorChecked = style({
59
+ backgroundColor: semanticVars.interactive.primary.default,
60
+ color: semanticVars.interactive.primaryFg,
61
+ });
62
+
63
+ export const indicatorIndeterminate = style({
64
+ color: semanticVars.text.primary,
65
+ });
66
+
67
+ export const icon = style({
68
+ width: 14,
69
+ height: 14,
70
+ "@media": {
71
+ "screen and (min-width: 640px)": {
72
+ width: 12,
73
+ height: 12,
74
+ },
75
+ },
76
+ });
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
4
+ import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group";
5
+ import clsx from "clsx";
6
+ import {
7
+ group,
8
+ root,
9
+ rootDisabled,
10
+ indicator,
11
+ indicatorChecked,
12
+ indicatorIndeterminate,
13
+ icon,
14
+ } from "./checkbox.css";
15
+
16
+ type CheckboxGroupProps = CheckboxGroupPrimitive.Props & {
17
+ className?: string;
18
+ style?: React.CSSProperties;
19
+ };
20
+
21
+ type CheckboxProps = Omit<CheckboxPrimitive.Root.Props, "style" | "className"> & {
22
+ className?: string;
23
+ style?: React.CSSProperties;
24
+ };
25
+
26
+ export function CheckboxGroup({ className, style, ...props }: CheckboxGroupProps) {
27
+ return <CheckboxGroupPrimitive className={clsx(group, className)} style={style} {...props} />;
28
+ }
29
+
30
+ function Checkbox({ className, style, ...props }: CheckboxProps) {
31
+ return (
32
+ <CheckboxPrimitive.Root
33
+ {...props}
34
+ render={(rootProps, { disabled }) => (
35
+ <span
36
+ {...rootProps}
37
+ className={clsx(root, disabled && rootDisabled, className)}
38
+ style={style}
39
+ data-slot="checkbox"
40
+ >
41
+ <CheckboxPrimitive.Indicator
42
+ render={(_indicatorProps, state) => (
43
+ <span
44
+ {..._indicatorProps}
45
+ className={clsx(
46
+ indicator,
47
+ state.checked && indicatorChecked,
48
+ state.indeterminate && indicatorIndeterminate,
49
+ )}
50
+ data-slot="checkbox-indicator"
51
+ >
52
+ {state.indeterminate ? (
53
+ <svg
54
+ aria-hidden="true"
55
+ className={icon}
56
+ fill="none"
57
+ height="24"
58
+ stroke="currentColor"
59
+ strokeLinecap="round"
60
+ strokeLinejoin="round"
61
+ strokeWidth="3"
62
+ viewBox="0 0 24 24"
63
+ width="24"
64
+ xmlns="http://www.w3.org/2000/svg"
65
+ >
66
+ <path d="M5.252 12h13.496" />
67
+ </svg>
68
+ ) : (
69
+ <svg
70
+ aria-hidden="true"
71
+ className={icon}
72
+ fill="none"
73
+ height="24"
74
+ stroke="currentColor"
75
+ strokeLinecap="round"
76
+ strokeLinejoin="round"
77
+ strokeWidth="3"
78
+ viewBox="0 0 24 24"
79
+ width="24"
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ >
82
+ <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
83
+ </svg>
84
+ )}
85
+ </span>
86
+ )}
87
+ />
88
+ </span>
89
+ )}
90
+ />
91
+ );
92
+ }
93
+
94
+ export { Checkbox };
@@ -0,0 +1 @@
1
+ export { Checkbox, CheckboxGroup } from "./checkbox";
@@ -0,0 +1,11 @@
1
+ import { style } from "@vanilla-extract/css";
2
+
3
+ export const icon = style({
4
+ width: "16px",
5
+ height: "16px",
6
+ fill: "none",
7
+ stroke: "currentColor",
8
+ strokeWidth: "2",
9
+ strokeLinecap: "round",
10
+ strokeLinejoin: "round",
11
+ });
@@ -0,0 +1,15 @@
1
+ import { IconButton, type IconButtonProps } from "../IconButton/icon-button";
2
+ import { icon } from "./close-button.css";
3
+
4
+ export type { IconButtonProps as CloseButtonProps };
5
+
6
+ export function CloseButton(props: IconButtonProps) {
7
+ return (
8
+ <IconButton {...props}>
9
+ <svg className={icon} viewBox="0 0 24 24" aria-hidden="true">
10
+ <line x1="18" y1="6" x2="6" y2="18" />
11
+ <line x1="6" y1="6" x2="18" y2="18" />
12
+ </svg>
13
+ </IconButton>
14
+ );
15
+ }
@@ -0,0 +1,2 @@
1
+ export { CloseButton } from "./close-button";
2
+ export type { CloseButtonProps } from "./close-button";
@@ -0,0 +1,123 @@
1
+ import { CaretDownIcon } from "@phosphor-icons/react";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { HexAlphaColorPicker, HexColorInput } from "react-colorful";
4
+ import { Button } from "../Button/button";
5
+ import { ColorSwatch } from "../ColorSwatch/color-swatch";
6
+ import { Field, FieldLabel } from "../Field/field";
7
+ import { Input } from "../Input/input";
8
+ import {
9
+ Popover,
10
+ PopoverArrow,
11
+ PopoverCompound,
12
+ PopoverPopup,
13
+ PopoverPositioner,
14
+ PopoverTrigger,
15
+ } from "../Popover/popover";
16
+ import { VStack } from "../Stack/stack";
17
+ import { Text } from "../Text/text";
18
+
19
+ const HEX_REGEX = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
20
+
21
+ type Props = {
22
+ value: string;
23
+ onChange: (color: string) => void;
24
+ label?: string;
25
+ };
26
+
27
+ export function ColorPicker({ value, onChange, label }: Props) {
28
+ const [open, setOpen] = useState(false);
29
+ const [internalValue, setInternalValue] = useState(value);
30
+ const lastValidRef = useRef(value);
31
+ const triggerRef = useRef<HTMLButtonElement>(null);
32
+ const prevValueRef = useRef(value);
33
+
34
+ useEffect(() => {
35
+ if (prevValueRef.current !== value) {
36
+ prevValueRef.current = value;
37
+ setInternalValue(value);
38
+ lastValidRef.current = value;
39
+ }
40
+ }, [value]);
41
+
42
+ const handleOpenChange = useCallback((nextOpen: boolean) => {
43
+ if (!nextOpen && triggerRef.current) {
44
+ triggerRef.current.focus();
45
+ }
46
+ setOpen(nextOpen);
47
+ }, []);
48
+
49
+ const handleHexInputChange = useCallback(
50
+ (newColor: string) => {
51
+ setInternalValue(newColor);
52
+ if (HEX_REGEX.test(newColor)) {
53
+ lastValidRef.current = newColor;
54
+ onChange(newColor);
55
+ }
56
+ },
57
+ [onChange],
58
+ );
59
+
60
+ const revertToLastValid = useCallback(() => {
61
+ setInternalValue(lastValidRef.current);
62
+ onChange(lastValidRef.current);
63
+ }, [onChange]);
64
+
65
+ return (
66
+ <Field>
67
+ <Popover open={open} onOpenChange={handleOpenChange}>
68
+ {label && (
69
+ <FieldLabel
70
+ onClick={() => {
71
+ triggerRef.current?.click();
72
+ triggerRef.current?.focus();
73
+ }}
74
+ >
75
+ {label}
76
+ </FieldLabel>
77
+ )}
78
+ <PopoverTrigger
79
+ ref={triggerRef}
80
+ aria-label={label ? `${label} color picker` : "Color picker"}
81
+ render={<Button type="button" variant="outline" />}
82
+ >
83
+ <ColorSwatch color={value} size={20} />
84
+ <Text variant="body2">{value}</Text>
85
+ <CaretDownIcon />
86
+ </PopoverTrigger>
87
+ <PopoverCompound.Portal>
88
+ <PopoverPositioner sideOffset={6} side="bottom" align="start">
89
+ <PopoverPopup>
90
+ <PopoverArrow />
91
+ <VStack gap="xs">
92
+ <HexAlphaColorPicker
93
+ color={internalValue}
94
+ onChange={(color) => {
95
+ setInternalValue(color);
96
+ lastValidRef.current = color;
97
+ onChange(color);
98
+ }}
99
+ />
100
+ <Input
101
+ size="sm"
102
+ render={
103
+ <HexColorInput
104
+ color={internalValue}
105
+ onChange={handleHexInputChange}
106
+ onBlur={() => {
107
+ if (!HEX_REGEX.test(internalValue)) {
108
+ revertToLastValid();
109
+ }
110
+ }}
111
+ prefixed
112
+ aria-label="Hex color value"
113
+ />
114
+ }
115
+ />
116
+ </VStack>
117
+ </PopoverPopup>
118
+ </PopoverPositioner>
119
+ </PopoverCompound.Portal>
120
+ </Popover>
121
+ </Field>
122
+ );
123
+ }
@@ -0,0 +1 @@
1
+ export * from "./color-picker";
@@ -0,0 +1,21 @@
1
+ type Props = {
2
+ color: string;
3
+ size?: number;
4
+ style?: React.CSSProperties;
5
+ className?: string;
6
+ };
7
+
8
+ export function ColorSwatch({ color, size = 16, style, className }: Props) {
9
+ return (
10
+ <span
11
+ className={className}
12
+ style={{
13
+ width: size,
14
+ height: size,
15
+ backgroundColor: color,
16
+ borderRadius: 4,
17
+ ...style,
18
+ }}
19
+ />
20
+ );
21
+ }
@@ -0,0 +1 @@
1
+ export * from "./color-swatch";