@peerbots/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 (81) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/publish.yml +42 -0
  4. package/.github/workflows/storybook.yml +46 -0
  5. package/.storybook/main.ts +28 -0
  6. package/.storybook/preview.ts +22 -0
  7. package/README.md +9 -0
  8. package/dist/index.css +1 -0
  9. package/dist/index.d.mts +704 -0
  10. package/dist/index.d.ts +704 -0
  11. package/dist/index.js +5 -0
  12. package/dist/index.mjs +5 -0
  13. package/package.json +60 -0
  14. package/src/charts/DistributionBarChart.stories.tsx +41 -0
  15. package/src/charts/DistributionBarChart.tsx +170 -0
  16. package/src/charts/DistributionHistogram.stories.tsx +56 -0
  17. package/src/charts/DistributionHistogram.tsx +193 -0
  18. package/src/charts/index.ts +10 -0
  19. package/src/global.d.ts +1 -0
  20. package/src/helpers/SEO.tsx +41 -0
  21. package/src/index.ts +6 -0
  22. package/src/styles/theme.css +60 -0
  23. package/src/ui/Alert.stories.tsx +41 -0
  24. package/src/ui/Alert.tsx +72 -0
  25. package/src/ui/Anchor.stories.tsx +25 -0
  26. package/src/ui/Anchor.tsx +32 -0
  27. package/src/ui/AuthFormUI.stories.tsx +67 -0
  28. package/src/ui/AuthFormUI.tsx +217 -0
  29. package/src/ui/BasePanel.stories.tsx +36 -0
  30. package/src/ui/BasePanel.tsx +59 -0
  31. package/src/ui/Button.stories.tsx +108 -0
  32. package/src/ui/Button.tsx +121 -0
  33. package/src/ui/Checkbox.stories.tsx +61 -0
  34. package/src/ui/Checkbox.tsx +45 -0
  35. package/src/ui/Collapsible.stories.tsx +91 -0
  36. package/src/ui/Collapsible.tsx +52 -0
  37. package/src/ui/Colors.stories.tsx +67 -0
  38. package/src/ui/Dialog.stories.tsx +29 -0
  39. package/src/ui/Dialog.tsx +56 -0
  40. package/src/ui/Dropdown.tsx +66 -0
  41. package/src/ui/Field.stories.tsx +181 -0
  42. package/src/ui/Field.tsx +108 -0
  43. package/src/ui/Icon.stories.tsx +192 -0
  44. package/src/ui/Icon.tsx +42 -0
  45. package/src/ui/IconRegistry.tsx +189 -0
  46. package/src/ui/Input.stories.tsx +67 -0
  47. package/src/ui/Input.tsx +43 -0
  48. package/src/ui/Label.stories.tsx +42 -0
  49. package/src/ui/Label.tsx +26 -0
  50. package/src/ui/NumberField.stories.tsx +86 -0
  51. package/src/ui/NumberField.tsx +116 -0
  52. package/src/ui/Popover.tsx +42 -0
  53. package/src/ui/Select.stories.tsx +74 -0
  54. package/src/ui/Select.tsx +122 -0
  55. package/src/ui/Separator.stories.tsx +61 -0
  56. package/src/ui/Separator.tsx +28 -0
  57. package/src/ui/SettingsPanel.stories.tsx +83 -0
  58. package/src/ui/SettingsPanel.tsx +81 -0
  59. package/src/ui/Skeleton.stories.tsx +43 -0
  60. package/src/ui/Skeleton.tsx +15 -0
  61. package/src/ui/Slider.stories.tsx +140 -0
  62. package/src/ui/Slider.tsx +95 -0
  63. package/src/ui/SliderWithNumberField.stories.tsx +101 -0
  64. package/src/ui/SliderWithNumberField.tsx +88 -0
  65. package/src/ui/Switch.stories.tsx +81 -0
  66. package/src/ui/Switch.tsx +60 -0
  67. package/src/ui/TabRadio.stories.tsx +153 -0
  68. package/src/ui/TabRadio.tsx +68 -0
  69. package/src/ui/TabSelection.stories.tsx +44 -0
  70. package/src/ui/TabSelection.tsx +91 -0
  71. package/src/ui/TextArea.stories.tsx +64 -0
  72. package/src/ui/TextArea.tsx +24 -0
  73. package/src/ui/Tooltip.stories.tsx +84 -0
  74. package/src/ui/Tooltip.tsx +61 -0
  75. package/src/ui/Typography.stories.tsx +87 -0
  76. package/src/ui/Typography.tsx +80 -0
  77. package/src/ui/index.ts +28 -0
  78. package/src/ui/utils.ts +6 -0
  79. package/tsconfig.json +12 -0
  80. package/vitest.config.ts +36 -0
  81. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,43 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { Skeleton } from "./Skeleton";
3
+
4
+ const meta: Meta<typeof Skeleton> = {
5
+ title: "UI/Skeleton",
6
+ component: Skeleton,
7
+ tags: ["autodocs"],
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Skeleton>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ className: "w-[250px] h-4",
19
+ },
20
+ };
21
+
22
+ export const Variations: Story = {
23
+ render: () => (
24
+ <div className="flex flex-col space-y-3">
25
+ {/* Circle Skeleton */}
26
+ <Skeleton className="h-12 w-12 rounded-full" />
27
+
28
+ {/* Rectangular Skeletons */}
29
+ <div className="space-y-2">
30
+ <Skeleton className="h-4 w-[250px]" />
31
+ <Skeleton className="h-4 w-[200px]" />
32
+ </div>
33
+
34
+ <div className="flex items-center space-x-4 mt-6">
35
+ <Skeleton className="h-12 w-12 rounded-full" />
36
+ <div className="space-y-2">
37
+ <Skeleton className="h-4 w-[250px]" />
38
+ <Skeleton className="h-4 w-[200px]" />
39
+ </div>
40
+ </div>
41
+ </div>
42
+ ),
43
+ };
@@ -0,0 +1,15 @@
1
+ import { HTMLAttributes } from "react";
2
+ import { cn } from "./utils";
3
+
4
+ export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
5
+ className?: string;
6
+ }
7
+
8
+ export function Skeleton({ className, ...props }: SkeletonProps) {
9
+ return (
10
+ <div
11
+ className={cn("animate-pulse rounded-md bg-gray-200/50", className)}
12
+ {...props}
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,140 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Slider } from "./Slider";
4
+ import { Input } from "./Input";
5
+ import { Heading, Text } from "./Typography";
6
+
7
+ const meta: Meta<typeof Slider> = {
8
+ title: "UI/Slider",
9
+ component: Slider,
10
+ argTypes: {
11
+ onChange: { action: "changed" },
12
+ },
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof Slider>;
17
+
18
+ const InteractiveSlider = (props: React.ComponentProps<typeof Slider>) => {
19
+ const [value, setValue] = useState(
20
+ Array.isArray(props.value) ? props.value[0] : (props.value ?? 0),
21
+ );
22
+
23
+ return (
24
+ <div className="space-y-2">
25
+ <div className="w-80">
26
+ <Slider
27
+ {...props}
28
+ value={value}
29
+ onChange={(v) => {
30
+ const newVal = Array.isArray(v) ? v[0] : v;
31
+ setValue(newVal);
32
+ props.onChange?.(v, null as unknown as Event);
33
+ }}
34
+ />
35
+ </div>
36
+ <Text variant="small" className="text-slate-500">
37
+ Value: {value}
38
+ </Text>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ const RangeChangeableSlider = ({
44
+ args,
45
+ }: {
46
+ args: React.ComponentProps<typeof Slider>;
47
+ }) => {
48
+ const [min, setMin] = useState(0);
49
+ const [max, setMax] = useState(100);
50
+ const [val, setVal] = useState(50);
51
+
52
+ return (
53
+ <div className="space-y-4 border p-4 rounded-lg bg-white shadow-sm">
54
+ <Slider
55
+ {...args}
56
+ min={min}
57
+ max={max}
58
+ value={val}
59
+ onChange={(v: number | number[]) => setVal(Array.isArray(v) ? v[0] : v)}
60
+ />
61
+ <div className="flex space-x-4">
62
+ <div className="flex flex-col gap-1">
63
+ <Text
64
+ variant="small"
65
+ className="uppercase text-slate-400 font-bold text-[10px]"
66
+ >
67
+ Min
68
+ </Text>
69
+ <Input
70
+ type="number"
71
+ value={min}
72
+ onChange={(e) => setMin(Number(e.target.value))}
73
+ className="w-20"
74
+ />
75
+ </div>
76
+ <div className="flex flex-col gap-1">
77
+ <Text
78
+ variant="small"
79
+ className="uppercase text-slate-400 font-bold text-[10px]"
80
+ >
81
+ Max
82
+ </Text>
83
+ <Input
84
+ type="number"
85
+ value={max}
86
+ onChange={(e) => setMax(Number(e.target.value))}
87
+ className="w-20"
88
+ />
89
+ </div>
90
+ <div className="flex flex-col justify-end">
91
+ <Text variant="small" className="text-slate-600 font-medium">
92
+ Current: {val}
93
+ </Text>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ );
98
+ };
99
+
100
+ export const Default: Story = {
101
+ args: {
102
+ min: 0,
103
+ max: 100,
104
+ value: 50,
105
+ },
106
+ render: (args) => <InteractiveSlider {...args} />,
107
+ };
108
+
109
+ export const Variations: Story = {
110
+ render: () => (
111
+ <div className="flex flex-col gap-12 p-4">
112
+ <div className="space-y-4">
113
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
114
+ Width Responsiveness
115
+ </Heading>
116
+ <div className="space-y-6">
117
+ <div className="space-y-1">
118
+ <Text variant="small" className="italic text-black">
119
+ Full Width (Container Default)
120
+ </Text>
121
+ <Slider defaultValue={30} />
122
+ </div>
123
+ <div className="space-y-1 w-40">
124
+ <Text variant="small" className="italic text-black">
125
+ Narrow (w-40)
126
+ </Text>
127
+ <Slider defaultValue={70} />
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div className="space-y-4">
133
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
134
+ Dynamic Range Test
135
+ </Heading>
136
+ <RangeChangeableSlider args={{}} />
137
+ </div>
138
+ </div>
139
+ ),
140
+ };
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import { Slider as BaseSlider } from "@base-ui/react";
3
+ import { cn } from "./utils";
4
+
5
+ export interface SliderProps {
6
+ min?: number;
7
+ max?: number;
8
+ step?: number;
9
+ value?: number;
10
+ defaultValue?: number;
11
+ disabled?: boolean;
12
+ onChange?: (value: number | number[], event: Event) => void;
13
+ className?: string;
14
+ name?: string;
15
+ icon?: React.ReactNode;
16
+ }
17
+
18
+ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
19
+ (
20
+ {
21
+ className,
22
+ min = 0,
23
+ max = 100,
24
+ step = 1,
25
+ value,
26
+ defaultValue,
27
+ disabled,
28
+ onChange,
29
+ name,
30
+ icon,
31
+ ...props
32
+ },
33
+ ref,
34
+ ) => {
35
+ const handleChange = (val: number | number[], event: Event) => {
36
+ if (onChange) {
37
+ onChange(val, event);
38
+ }
39
+ };
40
+
41
+ return (
42
+ <div className={cn("w-full flex items-center gap-4", className)}>
43
+ {icon && <div className="text-gray-500 shrink-0">{icon}</div>}
44
+
45
+ <div className="flex-grow flex items-center h-8 min-w-[120px]">
46
+ <BaseSlider.Root
47
+ ref={ref}
48
+ min={min}
49
+ max={max}
50
+ step={step}
51
+ value={
52
+ value !== undefined
53
+ ? Array.isArray(value)
54
+ ? value
55
+ : [value]
56
+ : undefined
57
+ }
58
+ defaultValue={
59
+ defaultValue !== undefined
60
+ ? Array.isArray(defaultValue)
61
+ ? defaultValue
62
+ : [defaultValue]
63
+ : undefined
64
+ }
65
+ disabled={disabled}
66
+ orientation="horizontal"
67
+ {...props}
68
+ onValueChange={(val, details) => {
69
+ const event =
70
+ (details as { event?: Event })?.event ||
71
+ (window.event as unknown as Event);
72
+ handleChange(val as number | number[], event);
73
+ }}
74
+ className={cn(
75
+ "relative flex items-center w-full h-full group touch-none select-none",
76
+ disabled && "opacity-50 cursor-not-allowed",
77
+ className,
78
+ )}
79
+ >
80
+ <BaseSlider.Control className="flex items-center w-full h-full relative cursor-pointer">
81
+ <BaseSlider.Track className="relative bg-gray-200 rounded-full w-full h-1.5 overflow-hidden transition-colors group-hover:bg-gray-300">
82
+ <BaseSlider.Indicator className="absolute bg-primary rounded-full h-full" />
83
+ </BaseSlider.Track>
84
+ <BaseSlider.Thumb className="z-10 block w-4.5 h-4.5 bg-white shadow-md rounded-full border-2 border-primary focus:outline-none focus:ring-4 focus:ring-primary/20 cursor-grab active:cursor-grabbing hover:scale-110 active:scale-95 transition-transform" />
85
+ </BaseSlider.Control>
86
+ </BaseSlider.Root>
87
+ </div>
88
+ {/* Hidden input to support form submission if needed */}
89
+ {name && <input type="hidden" name={name} value={value} />}
90
+ </div>
91
+ );
92
+ },
93
+ );
94
+
95
+ Slider.displayName = "Slider";
@@ -0,0 +1,101 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import SliderWithNumberField from "./SliderWithNumberField";
3
+ import React, { useState } from "react";
4
+ import { Heading, Text } from "./Typography";
5
+
6
+ const meta: Meta<typeof SliderWithNumberField> = {
7
+ title: "UI/SliderWithNumberField",
8
+ component: SliderWithNumberField,
9
+ argTypes: {
10
+ onChange: { action: "changed" },
11
+ min: { control: { type: "number" } },
12
+ max: { control: { type: "number" } },
13
+ step: { control: { type: "number" } },
14
+ disabled: { control: "boolean" },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof SliderWithNumberField>;
20
+
21
+ const InteractiveSliderWithNumberField = (
22
+ props: React.ComponentProps<typeof SliderWithNumberField>,
23
+ ) => {
24
+ const [value, setValue] = useState(props.value || props.defaultValue || 0);
25
+ return (
26
+ <SliderWithNumberField
27
+ {...props}
28
+ value={value}
29
+ onChange={(v) => {
30
+ setValue(v);
31
+ props.onChange?.(v as number, new Event("change")); // Match signature
32
+ }}
33
+ />
34
+ );
35
+ };
36
+
37
+ export const Default: Story = {
38
+ args: {
39
+ min: 0,
40
+ max: 100,
41
+ defaultValue: 50,
42
+ },
43
+ render: (args) => (
44
+ <div className="w-80 p-4">
45
+ <InteractiveSliderWithNumberField
46
+ {...(args as React.ComponentProps<typeof SliderWithNumberField>)}
47
+ />
48
+ </div>
49
+ ),
50
+ };
51
+
52
+ export const Variations: Story = {
53
+ render: () => (
54
+ <div className="flex flex-col gap-12 p-4">
55
+ <div className="space-y-4">
56
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
57
+ Responsive Layouts
58
+ </Heading>
59
+ <div className="space-y-8 max-w-lg">
60
+ <div className="space-y-2">
61
+ <Text variant="small" className="font-bold underline">
62
+ Flexible (Full width of container)
63
+ </Text>
64
+ <InteractiveSliderWithNumberField defaultValue={50} />
65
+ </div>
66
+
67
+ <div className="space-y-2 w-64 border-l border-r border-dotted px-2 bg-gray-50 py-4">
68
+ <Text variant="small" className="font-bold underline">
69
+ Narrow Container (w-64)
70
+ </Text>
71
+ <InteractiveSliderWithNumberField defaultValue={50} />
72
+ </div>
73
+
74
+ <div className="space-y-2 w-48 border-l border-r border-dotted px-2 bg-gray-50 py-4">
75
+ <Text variant="small" className="font-bold underline">
76
+ Very Narrow (w-48) - Wraps
77
+ </Text>
78
+ <InteractiveSliderWithNumberField defaultValue={50} />
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div className="space-y-4">
84
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
85
+ Configuration
86
+ </Heading>
87
+ <div className="space-y-4 max-w-sm">
88
+ <div className="space-y-1">
89
+ <Text variant="small">Custom Step (0.5)</Text>
90
+ <InteractiveSliderWithNumberField
91
+ step={0.5}
92
+ defaultValue={2.5}
93
+ min={0}
94
+ max={10}
95
+ />
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ ),
101
+ };
@@ -0,0 +1,88 @@
1
+ import React from "react";
2
+ import { Slider } from "./Slider";
3
+ import { NumberField } from "./NumberField";
4
+ import { cn } from "./utils";
5
+
6
+ export interface SliderWithNumberFieldProps {
7
+ min?: number;
8
+ max?: number;
9
+ step?: number;
10
+ value?: number;
11
+ defaultValue?: number;
12
+ disabled?: boolean;
13
+ onChange?: (value: number, event: Event) => void;
14
+ className?: string;
15
+ inputWidth?: string;
16
+ }
17
+
18
+ export const SliderWithNumberField = React.forwardRef<
19
+ HTMLDivElement,
20
+ SliderWithNumberFieldProps
21
+ >(
22
+ (
23
+ {
24
+ className,
25
+ min = 0,
26
+ max = 100,
27
+ step = 1,
28
+ value,
29
+ defaultValue,
30
+ disabled,
31
+ onChange,
32
+ inputWidth = "w-20",
33
+ ...props
34
+ },
35
+ ref,
36
+ ) => {
37
+ const handleChange = (val: number | number[], event: Event) => {
38
+ if (onChange) {
39
+ const newValue = Array.isArray(val) ? val[0] : val;
40
+ onChange(newValue, event);
41
+ }
42
+ };
43
+
44
+ return (
45
+ <div
46
+ ref={ref}
47
+ className={cn(
48
+ "flex flex-row items-center gap-4 w-full min-w-0 sm:flex-nowrap flex-wrap",
49
+ className,
50
+ )}
51
+ {...props}
52
+ >
53
+ <div className="flex-grow min-w-[120px]">
54
+ <Slider
55
+ min={min}
56
+ max={max}
57
+ step={step}
58
+ value={value}
59
+ defaultValue={defaultValue}
60
+ disabled={disabled}
61
+ onChange={handleChange}
62
+ className="w-full"
63
+ />
64
+ </div>
65
+ <div className={cn(inputWidth, "shrink-0")}>
66
+ <NumberField
67
+ value={value}
68
+ defaultValue={defaultValue}
69
+ min={min}
70
+ max={max}
71
+ step={step}
72
+ disabled={disabled}
73
+ onChange={(val, event) => {
74
+ if (val !== null) {
75
+ handleChange(val, event);
76
+ }
77
+ }}
78
+ showButtons={false}
79
+ />
80
+ </div>
81
+ </div>
82
+ );
83
+ },
84
+ );
85
+
86
+ SliderWithNumberField.displayName = "SliderWithNumberField";
87
+
88
+ export default SliderWithNumberField;
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Switch } from "./Switch";
3
+ import { useState } from "react";
4
+ import { Heading, Text } from "./Typography";
5
+
6
+ const meta: Meta<typeof Switch> = {
7
+ title: "UI/Switch",
8
+ component: Switch,
9
+ tags: ["autodocs"],
10
+ argTypes: {
11
+ checked: { control: "boolean" },
12
+ disabled: { control: "boolean" },
13
+ onChange: { action: "changed" },
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof Switch>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ "aria-label": "Default switch",
23
+ },
24
+ };
25
+
26
+ const InteractiveSwitch = () => {
27
+ const [checked, setChecked] = useState(false);
28
+ return (
29
+ <div className="flex flex-col gap-2">
30
+ <Switch
31
+ checked={checked}
32
+ onChange={(val) => setChecked(val)}
33
+ aria-label="Interactive switch"
34
+ />
35
+ <Text variant="small">
36
+ The switch is currently {checked ? "on" : "off"}.
37
+ </Text>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export const Variations: Story = {
43
+ render: () => (
44
+ <div className="flex flex-col gap-12 p-4">
45
+ <div className="space-y-4">
46
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
47
+ States
48
+ </Heading>
49
+ <div className="flex flex-col gap-4">
50
+ <div className="flex items-center gap-2">
51
+ <Switch defaultChecked={false} aria-label="Unchecked switch" />
52
+ <Text variant="small">Unchecked</Text>
53
+ </div>
54
+ <div className="flex items-center gap-2">
55
+ <Switch defaultChecked={true} aria-label="Checked switch" />
56
+ <Text variant="small">Checked</Text>
57
+ </div>
58
+ <div className="flex items-center gap-2">
59
+ <Switch disabled aria-label="Disabled switch" />
60
+ <Text variant="small">Disabled Unchecked</Text>
61
+ </div>
62
+ <div className="flex items-center gap-2">
63
+ <Switch
64
+ disabled
65
+ defaultChecked={true}
66
+ aria-label="Disabled checked switch"
67
+ />
68
+ <Text variant="small">Disabled Checked</Text>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div className="space-y-4">
74
+ <Heading level={4} className="text-sm font-medium text-black uppercase">
75
+ Interactive
76
+ </Heading>
77
+ <InteractiveSwitch />
78
+ </div>
79
+ </div>
80
+ ),
81
+ };
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import { Switch as BaseSwitch } from "@base-ui/react";
3
+ import { cn } from "./utils";
4
+
5
+ export interface SwitchProps extends React.AriaAttributes {
6
+ checked?: boolean;
7
+ defaultChecked?: boolean;
8
+ onChange?: (checked: boolean, event: Event) => void;
9
+ disabled?: boolean;
10
+ className?: string;
11
+ id?: string;
12
+ name?: string;
13
+ }
14
+
15
+ export const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
16
+ (
17
+ {
18
+ checked,
19
+ defaultChecked,
20
+ onChange,
21
+ disabled,
22
+ className,
23
+ id: externalId,
24
+ ...props
25
+ },
26
+ ref,
27
+ ) => {
28
+ const autoId = React.useId();
29
+ const id = externalId ?? autoId;
30
+ return (
31
+ <div className={cn("flex items-center gap-2", className)}>
32
+ <BaseSwitch.Root
33
+ ref={ref}
34
+ id={id}
35
+ checked={checked}
36
+ defaultChecked={defaultChecked}
37
+ onCheckedChange={(checked, eventDetails) => {
38
+ onChange?.(checked, eventDetails?.event as Event);
39
+ }}
40
+ disabled={disabled}
41
+ className={cn(
42
+ "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
43
+ "data-[checked]:bg-primary data-[unchecked]:bg-gray-200",
44
+ disabled && "opacity-50 cursor-not-allowed",
45
+ )}
46
+ {...props}
47
+ >
48
+ <BaseSwitch.Thumb
49
+ className={cn(
50
+ "pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out",
51
+ "data-[checked]:translate-x-5 data-[unchecked]:translate-x-0",
52
+ )}
53
+ />
54
+ </BaseSwitch.Root>
55
+ </div>
56
+ );
57
+ },
58
+ );
59
+
60
+ Switch.displayName = "Switch";