@eggspot/ui 0.0.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 (74) hide show
  1. package/eslint.config.js +4 -0
  2. package/package.json +66 -0
  3. package/postcss.config.mjs +1 -0
  4. package/src/components/Button.machine.tsx +50 -0
  5. package/src/components/Button.tsx +249 -0
  6. package/src/components/Button.variants.tsx +186 -0
  7. package/src/components/ButtonGroup.tsx +56 -0
  8. package/src/components/Calendar.tsx +275 -0
  9. package/src/components/Calendar.utils.tsx +22 -0
  10. package/src/components/Checkbox.tsx +199 -0
  11. package/src/components/ConfirmDialog.tsx +183 -0
  12. package/src/components/DashboardLayout/DashboardLayout.tsx +348 -0
  13. package/src/components/DashboardLayout/SidebarNav.tsx +509 -0
  14. package/src/components/DashboardLayout/index.ts +33 -0
  15. package/src/components/DataTable/DataTable.tsx +557 -0
  16. package/src/components/DataTable/DataTableColumnHeader.tsx +122 -0
  17. package/src/components/DataTable/DataTableDisplaySettings.tsx +265 -0
  18. package/src/components/DataTable/DataTableFloatingBar.tsx +44 -0
  19. package/src/components/DataTable/DataTablePagination.tsx +168 -0
  20. package/src/components/DataTable/DataTableStates.tsx +69 -0
  21. package/src/components/DataTable/DataTableToolbarContainer.tsx +47 -0
  22. package/src/components/DataTable/hooks/use-data-table-settings.ts +101 -0
  23. package/src/components/DataTable/index.ts +7 -0
  24. package/src/components/DataTable/types/data-table.ts +97 -0
  25. package/src/components/DatePicker.tsx +213 -0
  26. package/src/components/DatePicker.utils.tsx +38 -0
  27. package/src/components/Datefield.tsx +109 -0
  28. package/src/components/Datefield.utils.ts +10 -0
  29. package/src/components/Dialog.tsx +167 -0
  30. package/src/components/Field.tsx +49 -0
  31. package/src/components/Filter/Filter.store.tsx +122 -0
  32. package/src/components/Filter/Filter.tsx +11 -0
  33. package/src/components/Filter/Filter.types.ts +107 -0
  34. package/src/components/Filter/FilterBar.tsx +38 -0
  35. package/src/components/Filter/FilterBuilder.tsx +158 -0
  36. package/src/components/Filter/FilterField/DateModeRowValue.tsx +250 -0
  37. package/src/components/Filter/FilterField/FilterAsyncSelect.tsx +191 -0
  38. package/src/components/Filter/FilterField/FilterDateMode.tsx +241 -0
  39. package/src/components/Filter/FilterField/FilterDateRange.tsx +169 -0
  40. package/src/components/Filter/FilterField/FilterSelect.tsx +208 -0
  41. package/src/components/Filter/FilterField/FilterSingleDate.tsx +277 -0
  42. package/src/components/Filter/FilterField/OptionItem.tsx +112 -0
  43. package/src/components/Filter/FilterField/index.ts +6 -0
  44. package/src/components/Filter/FilterRow.tsx +527 -0
  45. package/src/components/Filter/index.ts +17 -0
  46. package/src/components/Form.tsx +195 -0
  47. package/src/components/Heading.tsx +41 -0
  48. package/src/components/Input.tsx +221 -0
  49. package/src/components/InputOTP.tsx +78 -0
  50. package/src/components/Label.tsx +65 -0
  51. package/src/components/Layout.tsx +129 -0
  52. package/src/components/ListBox.tsx +97 -0
  53. package/src/components/Menu.tsx +152 -0
  54. package/src/components/NativeSelect.tsx +77 -0
  55. package/src/components/NumberInput.tsx +114 -0
  56. package/src/components/Popover.tsx +44 -0
  57. package/src/components/Provider.tsx +22 -0
  58. package/src/components/RadioGroup.tsx +191 -0
  59. package/src/components/Resizable.tsx +71 -0
  60. package/src/components/ScrollArea.tsx +57 -0
  61. package/src/components/Select.tsx +626 -0
  62. package/src/components/Select.utils.tsx +64 -0
  63. package/src/components/Separator.tsx +25 -0
  64. package/src/components/Sheet.tsx +147 -0
  65. package/src/components/Sonner.tsx +96 -0
  66. package/src/components/Spinner.tsx +30 -0
  67. package/src/components/Switch.tsx +51 -0
  68. package/src/components/Text.tsx +35 -0
  69. package/src/components/Tooltip.tsx +58 -0
  70. package/src/consts/config.ts +2 -0
  71. package/src/hooks/.gitkeep +0 -0
  72. package/src/lib/utils.ts +10 -0
  73. package/tsconfig.json +11 -0
  74. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,4 @@
1
+ import { config } from "@workspace/eslint-config/react-internal"
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@eggspot/ui",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "scripts": {
7
+ "lint": "eslint . --max-warnings 0"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "dependencies": {
13
+ "@dnd-kit/core": "^6.3.1",
14
+ "@dnd-kit/modifiers": "^9.0.0",
15
+ "@dnd-kit/sortable": "^10.0.0",
16
+ "@dnd-kit/utilities": "^3.2.2",
17
+ "@hookform/resolvers": "catalog:",
18
+ "@internationalized/date": "^3.10.0",
19
+ "@radix-ui/react-scroll-area": "^1.2.10",
20
+ "@radix-ui/react-slot": "^1.2.4",
21
+ "@radix-ui/react-use-controllable-state": "^1.2.2",
22
+ "@tanstack/react-table": "catalog:",
23
+ "@tanstack/react-virtual": "^3.13.13",
24
+ "@xstate/react": "^6.0.0",
25
+ "@xstate/store": "^3.11.2",
26
+ "class-variance-authority": "^0.7.1",
27
+ "clsx": "^2.1.1",
28
+ "dayjs": "^1.11.19",
29
+ "input-otp": "^1.4.2",
30
+ "lodash": "catalog:",
31
+ "lucide-react": "catalog:",
32
+ "next-themes": "catalog:",
33
+ "radix-ui": "^1.4.3",
34
+ "react": "catalog:",
35
+ "react-aria-components": "^1.13.0",
36
+ "react-dom": "catalog:",
37
+ "react-hook-form": "catalog:",
38
+ "react-resizable-panels": "^3.0.6",
39
+ "react-use": "catalog:",
40
+ "sonner": "^2.0.7",
41
+ "tailwind-merge": "^3.3.1",
42
+ "tailwind-variants": "^3.1.1",
43
+ "tw-animate-css": "^1.3.6",
44
+ "xstate": "^5.24.0",
45
+ "zod": "catalog:"
46
+ },
47
+ "devDependencies": {
48
+ "@eggspot/theme": "workspace:*",
49
+ "@turbo/gen": "^2.5.5",
50
+ "@types/lodash": "catalog:",
51
+ "@types/node": "catalog:",
52
+ "@types/react": "catalog:",
53
+ "@types/react-dom": "catalog:",
54
+ "@workspace/eslint-config": "workspace:*",
55
+ "@workspace/typescript-config": "workspace:*",
56
+ "typescript": "catalog:"
57
+ },
58
+ "exports": {
59
+ "./globals.css": "./src/styles/globals.css",
60
+ "./postcss.config": "./postcss.config.mjs",
61
+ "./lib/*": "./src/lib/*.ts",
62
+ "./components/*": "./src/components/*.tsx",
63
+ "./hooks/*": "./src/hooks/*.ts",
64
+ "./consts/*": "./src/consts/*.ts"
65
+ }
66
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@eggspot/theme/postcss.config"
@@ -0,0 +1,50 @@
1
+ import { assign, createMachine } from "xstate"
2
+
3
+ export const countdownMachine = createMachine({
4
+ /** @xstate-layout N4IgpgJg5mDOIC5QGMD2BXAdgFwqg7pgHQCSmAltuQIYA2AxAMoAqAggErMDaADALqJQAB1SxK5VJkEgAHogC0ARgBMADiIB2ACwBWVVoCcqnhoDMGsxoA0IAJ6JzGojoNrl7jTwBsXjaoC+-jZoWLgExADCGDjkmFD0MrDY1NhgRNQAZqkATgAUijyFAJT0ITh4hERRobFQvAJIICJiVJLScggGOkQGGnoGpl08Blq+qjb2CMqKTjOqGq5epvpGy4HB0WGV1TFxCUkpaZk5+YU8JWVbkZu1XIoNwqLibY0dWspEJu+K8zoTiDpFERvr9AkEQJhUBA4NJLhUpI1ms8EaAOvJTCoiKZTEtVKoCtpvF5-gh0V5Pn4VDp1iA4eFSBQqHRpEjWijZAplF4tFicct8V8iSTHM5XMYeNiTPyaXTtjc4iynmz2ogeMLVOTqeDZcQAGKxciwAAWkEVLQk7I6arsAOmYP8QA */
5
+ types: {
6
+ events: {} as { type: "START" },
7
+ context: {} as { count: number; initialCount: number },
8
+ input: {} as { initialCount: number },
9
+ },
10
+ id: "countdown",
11
+ initial: "Initial",
12
+ context: ({ input }) => ({
13
+ count: input.initialCount,
14
+ initialCount: input.initialCount,
15
+ }),
16
+ states: {
17
+ Initial: {
18
+ on: {
19
+ START: {
20
+ target: "Counting",
21
+ },
22
+ },
23
+ },
24
+ Counting: {
25
+ after: {
26
+ 1000: [
27
+ {
28
+ guard: ({ context }) => context.count === 1,
29
+ target: "Finished",
30
+ },
31
+ {
32
+ actions: assign(({ context }) => ({
33
+ count: context.count - 1,
34
+ })),
35
+ target: "Counting",
36
+ reenter: true,
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ Finished: {
42
+ always: {
43
+ target: "Initial",
44
+ actions: assign(({ context }) => ({
45
+ count: context.initialCount,
46
+ })),
47
+ },
48
+ },
49
+ },
50
+ })
@@ -0,0 +1,249 @@
1
+ "use client"
2
+
3
+ import { countdownMachine } from "@eggspot/ui/components/Button.machine"
4
+ import {
5
+ ButtonVariantProps,
6
+ buttonVariants,
7
+ } from "@eggspot/ui/components/Button.variants"
8
+ import { Center, Float } from "@eggspot/ui/components/Layout"
9
+ import { Spinner } from "@eggspot/ui/components/Spinner"
10
+ import { Tooltip, TooltipTrigger } from "@eggspot/ui/components/Tooltip"
11
+ import { cn, formatCount } from "@eggspot/ui/lib/utils"
12
+ import { useMachine } from "@xstate/react"
13
+ import { cva } from "class-variance-authority"
14
+ import { Slot } from "radix-ui"
15
+ import {
16
+ Button as AriaButton,
17
+ Focusable,
18
+ PressEvent,
19
+ type ButtonProps as AriaButtonProps,
20
+ } from "react-aria-components"
21
+
22
+ interface CommonButtonProps
23
+ extends AriaButtonProps,
24
+ React.RefAttributes<HTMLButtonElement>,
25
+ ButtonVariantProps {
26
+ /* If true, the button will be rendered as a child */
27
+ asChild?: boolean
28
+
29
+ /** If true, the button will show a loading state */
30
+ isLoading?: boolean
31
+
32
+ /** The delay of the tooltip */
33
+ tooltipDelay?: number
34
+
35
+ /** The test id of the button */
36
+ testId?: string
37
+
38
+ /** If true, the button will show a badge */
39
+ withBadge?: boolean
40
+
41
+ /** The badge number of the button */
42
+ badgeNumber?: number
43
+ }
44
+
45
+ interface IconButtonProps extends CommonButtonProps {
46
+ mode: "icon"
47
+
48
+ /** Tooltip text for the button. */
49
+ tooltip?: string
50
+
51
+ /** The placement of the tooltip */
52
+ tooltipPlacement?: "top" | "bottom" | "left" | "right"
53
+ }
54
+ interface DefaultButtonProps extends CommonButtonProps {
55
+ mode?: "default"
56
+
57
+ /** Optional tooltip for default buttons */
58
+ tooltip?: string
59
+
60
+ /** The left icon of the button */
61
+ leftIcon?: React.ReactNode
62
+
63
+ /** The right icon of the button */
64
+ rightIcon?: React.ReactNode
65
+
66
+ /** If provided, the button will show a countdown */
67
+ countdown?: number
68
+
69
+ /** The placement of the tooltip */
70
+ tooltipPlacement?: "top" | "bottom" | "left" | "right"
71
+ }
72
+
73
+ type ButtonProps = IconButtonProps | DefaultButtonProps
74
+
75
+ function Button(props: ButtonProps) {
76
+ const {
77
+ mode,
78
+ size,
79
+ intent,
80
+ variant = "solid",
81
+ className,
82
+ fullWidth,
83
+ asChild = false,
84
+ isLoading = false,
85
+ children,
86
+ isDisabled,
87
+ tooltip,
88
+ tooltipDelay = 0,
89
+ tooltipPlacement = "bottom",
90
+ withBadge,
91
+ badgeNumber,
92
+ testId,
93
+ onPress,
94
+ ...restProps
95
+ } = props
96
+
97
+ const isDefault = mode === "default" || mode === undefined
98
+ const defaultIntent = variant === "solid" ? "primary" : "secondary"
99
+
100
+ const leftIcon = isDefault ? props.leftIcon : undefined
101
+ const rightIcon = isDefault ? props.rightIcon : undefined
102
+
103
+ // countdown and state machine setup
104
+ const hasCountdown = isDefault && props.countdown !== undefined
105
+ const initialCount = isDefault ? props.countdown : undefined
106
+ const [state, send] = useMachine(countdownMachine, {
107
+ input: {
108
+ initialCount: initialCount || 0,
109
+ },
110
+ })
111
+
112
+ const count = state.context.count
113
+ const isCounting = hasCountdown && state.matches("Counting")
114
+ const countdownProps = hasCountdown
115
+ ? {
116
+ onPress: (e: PressEvent) => {
117
+ send({ type: "START" })
118
+ onPress?.(e)
119
+ },
120
+ }
121
+ : {}
122
+
123
+ // rendering
124
+ const Comp = (asChild ? Slot.Root : AriaButton) as any
125
+ const isLoadingOrDisabled = isLoading || isDisabled || isCounting
126
+
127
+ const ariaProps: AriaButtonProps = asChild
128
+ ? {}
129
+ : {
130
+ isPending: isLoading,
131
+ isDisabled: isLoadingOrDisabled,
132
+ }
133
+
134
+ const button = (
135
+ <Comp
136
+ type="button"
137
+ aria-label={tooltip}
138
+ data-testid={testId}
139
+ data-has-right-element={!!rightIcon}
140
+ data-has-left-element={!!leftIcon}
141
+ className={cn(
142
+ buttonVariants({
143
+ mode,
144
+ size,
145
+ intent: intent || defaultIntent,
146
+ variant,
147
+ className,
148
+ fullWidth,
149
+ })
150
+ )}
151
+ {...ariaProps}
152
+ {...countdownProps}
153
+ {...restProps}
154
+ >
155
+ {isLoading && (
156
+ <Center className="spinner absolute inset-0 text-transparent">
157
+ <Spinner />
158
+ </Center>
159
+ )}
160
+
161
+ {/* badge */}
162
+ {withBadge && (
163
+ <Float position="top-right">
164
+ <ButtonBadge
165
+ badgeNumber={badgeNumber || 0}
166
+ className="border-gray-1 border"
167
+ />
168
+ </Float>
169
+ )}
170
+
171
+ {/* left icon */}
172
+ {leftIcon}
173
+
174
+ {/* children */}
175
+ <Slot.Slottable>{children as React.ReactNode}</Slot.Slottable>
176
+
177
+ {/* countdown */}
178
+ {hasCountdown && isCounting && (
179
+ <ButtonBadge
180
+ intent="secondary"
181
+ badgeNumber={count}
182
+ maxVisibleNumber={99}
183
+ />
184
+ )}
185
+
186
+ {/* right icon */}
187
+ {rightIcon}
188
+ </Comp>
189
+ )
190
+
191
+ if (tooltip) {
192
+ return (
193
+ <TooltipTrigger delay={tooltipDelay} closeDelay={100}>
194
+ {asChild ? <Focusable>{button}</Focusable> : button}
195
+ <Tooltip style={{ zIndex: 100000000 }} placement={tooltipPlacement}>
196
+ {tooltip}
197
+ </Tooltip>
198
+ </TooltipTrigger>
199
+ )
200
+ }
201
+
202
+ return button
203
+ }
204
+
205
+ const badgeVariants = cva(
206
+ "bg-error-9 flex min-h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] leading-[14px] text-white",
207
+ {
208
+ variants: {
209
+ intent: {
210
+ primary: "bg-accent-9",
211
+ secondary: "bg-gray-9",
212
+ danger: "bg-error-9",
213
+ },
214
+ },
215
+ defaultVariants: {
216
+ intent: "danger",
217
+ },
218
+ }
219
+ )
220
+
221
+ interface ButtonBadgeProps {
222
+ badgeNumber?: number
223
+ className?: string
224
+ maxVisibleNumber?: number
225
+ intent?: "primary" | "secondary" | "danger"
226
+ }
227
+
228
+ function ButtonBadge({
229
+ badgeNumber,
230
+ className,
231
+ maxVisibleNumber = 9,
232
+ intent,
233
+ }: ButtonBadgeProps) {
234
+ return (
235
+ <div
236
+ className={cn(
237
+ badgeVariants({ intent }),
238
+ badgeNumber && badgeNumber > maxVisibleNumber && "pr-[3px]",
239
+ !badgeNumber && "min-h-3 min-w-3",
240
+ className
241
+ )}
242
+ >
243
+ {badgeNumber ? formatCount(badgeNumber, maxVisibleNumber) : ""}
244
+ </div>
245
+ )
246
+ }
247
+
248
+ export { Button, ButtonBadge, buttonVariants }
249
+ export type { ButtonProps }
@@ -0,0 +1,186 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+
3
+ export const buttonVariants = cva(
4
+ [
5
+ "text-gray-12 ring-offset-gray-1 relative inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-md text-sm font-medium whitespace-nowrap no-underline transition-[transform,background-color] duration-100 select-none",
6
+ /* SVGs */
7
+ "[&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:stroke-2",
8
+ /* Pressed */
9
+ "active:transform-[scale(0.98)]",
10
+ /* Disabled */
11
+ "disabled:cursor-not-allowed disabled:opacity-55",
12
+ /* Loading */
13
+ "data-pending:text-transparent data-pending:[&>.spinner]:text-white",
14
+ /* Focus Visible */
15
+ "focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
16
+ /* Resets */
17
+ "focus-visible:outline-none",
18
+ ],
19
+ {
20
+ variants: {
21
+ mode: {
22
+ default: "",
23
+ icon: "",
24
+ },
25
+ size: {
26
+ sm: [
27
+ "h-7 px-2 text-[13px]",
28
+ /* Optically align elements */
29
+ "data-[has-left-element=true]:pl-1.5 data-[has-right-element=true]:pr-1.5",
30
+ ],
31
+ md: [
32
+ "h-8 px-3 py-2",
33
+ /* Optically align elements */
34
+ "data-[has-left-element=true]:pl-2 data-[has-right-element=true]:pr-2",
35
+ ],
36
+ lg: [
37
+ "h-10 gap-2 px-4",
38
+ /* Optically align elements */
39
+ "data-[has-left-element=true]:pl-[10px] data-[has-right-element=true]:pr-[10px]",
40
+ ],
41
+ },
42
+ intent: {
43
+ primary: "ring-accent-7",
44
+ secondary: "ring-gray-7",
45
+ danger: "ring-error-7",
46
+ },
47
+ variant: {
48
+ solid: "",
49
+ outline: "",
50
+ ghost: "",
51
+ minimal: "",
52
+ },
53
+ fullWidth: {
54
+ true: "w-full",
55
+ },
56
+ },
57
+ compoundVariants: [
58
+ /* Primary */
59
+ {
60
+ variant: "solid",
61
+ intent: "primary",
62
+ className: [
63
+ "bg-accent-9 text-accent-contrast hover:not-disabled:bg-accent-10",
64
+ "border border-black/5 dark:border-white/10",
65
+ ],
66
+ },
67
+ {
68
+ variant: "outline",
69
+ intent: "primary",
70
+ className: [
71
+ "bg-accent-2 border-accent-7 text-accent-11 hover:not-disabled:bg-accent-3 border",
72
+ "data-pending:[&>.spinner]:text-accent-11",
73
+ ],
74
+ },
75
+ {
76
+ variant: "ghost",
77
+ intent: "primary",
78
+ className: [
79
+ "text-accent-11 hover:not-disabled:bg-accent-3",
80
+ "data-pending:[&>.spinner]:text-accent-11 data-pending:bg-accent-3",
81
+ ],
82
+ },
83
+ {
84
+ variant: "minimal",
85
+ intent: "primary",
86
+ className: [
87
+ "text-accent-11 bg-accent-3 hover:not-disabled:bg-accent-4",
88
+ "data-pending:[&>.spinner]:text-accent-11",
89
+ ],
90
+ },
91
+
92
+ /* Secondary */
93
+ {
94
+ variant: "solid",
95
+ intent: "secondary",
96
+ className: [
97
+ "bg-gray-3 border-gray-3 hover:not-disabled:bg-gray-4 hover:not-disabled:border-gray-4 border",
98
+ "data-pending:[&>.spinner]:text-gray-11",
99
+ ],
100
+ },
101
+ {
102
+ variant: "outline",
103
+ intent: "secondary",
104
+ className: [
105
+ "bg-gray-2 border-gray-7 hover:not-disabled:bg-gray-3 border",
106
+ "data-pending:[&>.spinner]:text-gray-11",
107
+ ],
108
+ },
109
+ {
110
+ variant: "ghost",
111
+ intent: "secondary",
112
+ className: [
113
+ "hover:not-disabled:bg-gray-3",
114
+ "data-pending:[&>.spinner]:text-gray-11 data-pending:bg-gray-3",
115
+ ],
116
+ },
117
+ {
118
+ variant: "minimal",
119
+ intent: "secondary",
120
+ className: [
121
+ "bg-gray-3 hover:not-disabled:bg-gray-4",
122
+ "data-pending:[&>.spinner]:text-gray-11",
123
+ ],
124
+ },
125
+
126
+ /* Danger */
127
+ {
128
+ variant: "solid",
129
+ intent: "danger",
130
+ className: [
131
+ "bg-error-9 hover:not-disabled:bg-error-10 text-white",
132
+ "border border-black/5 dark:border-white/10",
133
+ ],
134
+ },
135
+ {
136
+ variant: "outline",
137
+ intent: "danger",
138
+ className: [
139
+ "bg-error-2 text-error-11 border-error-7 hover:not-disabled:bg-error-3 border",
140
+ "data-pending:[&>.spinner]:text-error-11",
141
+ ],
142
+ },
143
+ {
144
+ variant: "ghost",
145
+ intent: "danger",
146
+ className: [
147
+ "text-error-11 hover:not-disabled:bg-error-3",
148
+ "data-pending:[&>.spinner]:text-error-11 data-pending:bg-error-3",
149
+ ],
150
+ },
151
+ {
152
+ variant: "minimal",
153
+ intent: "danger",
154
+ className: [
155
+ "text-error-11 bg-error-3 hover:not-disabled:bg-error-4",
156
+ "data-pending:[&>.spinner]:text-error-11",
157
+ ],
158
+ },
159
+
160
+ /* Mode */
161
+ {
162
+ mode: "icon",
163
+ size: "sm",
164
+ className: ["size-7 [&_svg]:size-4"],
165
+ },
166
+ {
167
+ mode: "icon",
168
+ size: "md",
169
+ className: ["size-8 [&_svg]:size-[18px]"],
170
+ },
171
+ {
172
+ mode: "icon",
173
+ size: "lg",
174
+ className: ["size-10 [&_svg]:size-5"],
175
+ },
176
+ ],
177
+ defaultVariants: {
178
+ variant: "solid",
179
+ intent: "primary",
180
+ size: "md",
181
+ mode: "default",
182
+ },
183
+ }
184
+ )
185
+
186
+ export type ButtonVariantProps = VariantProps<typeof buttonVariants>
@@ -0,0 +1,56 @@
1
+ import { Separator } from "@eggspot/ui/components/Separator"
2
+ import { cn } from "@eggspot/ui/lib/utils"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ const buttonGroupVariants = cva(
6
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 [&>input]:flex-1",
7
+ {
8
+ variants: {
9
+ orientation: {
10
+ horizontal:
11
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
12
+ vertical:
13
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ orientation: "horizontal",
18
+ },
19
+ }
20
+ )
21
+
22
+ function ButtonGroup({
23
+ className,
24
+ orientation,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
27
+ return (
28
+ <div
29
+ role="group"
30
+ data-slot="button-group"
31
+ data-orientation={orientation}
32
+ className={cn(buttonGroupVariants({ orientation }), className)}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function ButtonGroupSeparator({
39
+ className,
40
+ orientation = "vertical",
41
+ ...props
42
+ }: React.ComponentProps<typeof Separator>) {
43
+ return (
44
+ <Separator
45
+ data-slot="button-group-separator"
46
+ orientation={orientation}
47
+ className={cn(
48
+ "bg-input relative m-0! self-stretch data-[orientation=vertical]:h-auto",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ export { ButtonGroup, ButtonGroupSeparator, buttonGroupVariants }