@spark-web/switch 5.0.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @spark-web/switch
2
+
3
+ ## 5.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#757](https://github.com/brighte-labs/spark-web/pull/757)
8
+ [`8a40ef9`](https://github.com/brighte-labs/spark-web/commit/8a40ef9a87273c1755f36f834a41ddc7c9fa55df)
9
+ Thanks [@jacobporci-brighte](https://github.com/jacobporci-brighte)! - Created
10
+ Switch component
11
+
12
+ ## 5.0.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Initial release of the Switch component
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: Switch
3
+ storybookPath: forms-switch--default
4
+ isExperimentalPackage: false
5
+ ---
6
+
7
+ A Switch is a binary toggle control that represents on/off states. Built on top
8
+ of `@radix-ui/react-switch` for full accessibility support including keyboard
9
+ navigation, ARIA attributes, and screen reader announcements.
10
+
11
+ ## Examples
12
+
13
+ ### Controlled
14
+
15
+ Switches can be both controlled and uncontrolled. To control a switch provide
16
+ the `checked` state with a value you control, as well as an `onCheckedChange`
17
+ function to set the new value when the switch is toggled.
18
+
19
+ ```jsx live
20
+ const [checked, setChecked] = React.useState(false);
21
+
22
+ return (
23
+ <Stack gap="large">
24
+ <Switch checked={checked} onCheckedChange={setChecked}>
25
+ <Text>Enable feature</Text>
26
+ </Switch>
27
+ {checked && <Text>Feature is enabled</Text>}
28
+ </Stack>
29
+ );
30
+ ```
31
+
32
+ ### Size
33
+
34
+ Switches are available in two sizes: `small` and `medium`.
35
+
36
+ ```jsx live
37
+ <Stack gap="large">
38
+ <Fieldset legend="Switch variations (small)" gap="large">
39
+ <Switch size="small" checked={false}>
40
+ Unchecked
41
+ </Switch>
42
+ <Switch size="small" checked>
43
+ Checked
44
+ </Switch>
45
+ <Switch size="small" disabled>
46
+ Disabled
47
+ </Switch>
48
+ <Switch size="small" checked disabled>
49
+ Checked + disabled
50
+ </Switch>
51
+ </Fieldset>
52
+ <Divider />
53
+ <Fieldset legend="Switch variations (medium)" gap="large">
54
+ <Switch size="medium" checked={false}>
55
+ Unchecked
56
+ </Switch>
57
+ <Switch size="medium" checked>
58
+ Checked
59
+ </Switch>
60
+ <Switch size="medium" disabled>
61
+ Disabled
62
+ </Switch>
63
+ <Switch size="medium" checked disabled>
64
+ Checked + disabled
65
+ </Switch>
66
+ </Fieldset>
67
+ </Stack>
68
+ ```
69
+
70
+ ### Message and tone
71
+
72
+ The `message` is used to communicate the status of a field, such as an error
73
+ message. This will be announced on focus and can be combined with a `tone` to
74
+ illustrate intent. The supported tones are: `critical`, `positive` and
75
+ `neutral`.
76
+
77
+ ```jsx live
78
+ <Fieldset legend="Message and tone" gap="large">
79
+ <Switch message="Critical message" tone="critical">
80
+ Critical
81
+ </Switch>
82
+ <Switch message="Positive message" tone="positive">
83
+ Positive
84
+ </Switch>
85
+ <Switch message="Neutral message" tone="neutral">
86
+ Neutral
87
+ </Switch>
88
+ </Fieldset>
89
+ ```
90
+
91
+ ## Props
92
+
93
+ ### Switch
94
+
95
+ <PropsTable displayName="Switch" />
96
+
97
+ [data-attribute-map]:
98
+ https://github.com/brighte-labs/spark-web/blob/e7f6f4285b4cfd876312cc89fbdd094039aa239a/packages/utils/src/internal/buildDataAttributes.ts#L1
@@ -0,0 +1,2 @@
1
+ export { Switch } from "./switch.js";
2
+ export type { SwitchProps, SwitchSize } from "./switch.js";
@@ -0,0 +1,40 @@
1
+ import * as RadixSwitch from '@radix-ui/react-switch';
2
+ import type { Tone } from '@spark-web/field';
3
+ import type { DataAttributeMap } from '@spark-web/utils/internal';
4
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
5
+ export type SwitchSize = 'small' | 'medium';
6
+ export type SwitchProps = {
7
+ /** The label content displayed next to the switch. */
8
+ children: ReactNode;
9
+ /** Sets data attributes on the component. */
10
+ data?: DataAttributeMap;
11
+ /** When true, the switch will be on. Use with `onCheckedChange` for controlled usage. */
12
+ checked?: boolean;
13
+ /** The initial checked state for uncontrolled usage. */
14
+ defaultChecked?: boolean;
15
+ /** When true, the switch will be disabled. */
16
+ disabled?: boolean;
17
+ /** Sets a unique identifier on the component. */
18
+ id?: string;
19
+ /** Provide a message, informing the user about changes in state. */
20
+ message?: string;
21
+ /** The name submitted with the switch's owning form. */
22
+ name?: string;
23
+ /** Callback fired when the checked state changes. */
24
+ onCheckedChange?: (checked: boolean) => void;
25
+ /** When true, indicates the switch is required in a form. */
26
+ required?: boolean;
27
+ /** The size of the switch. */
28
+ size?: SwitchSize;
29
+ /** Provide a tone to influence the message appearance. */
30
+ tone?: Tone;
31
+ /** The value submitted with the switch's owning form when checked. */
32
+ value?: string;
33
+ };
34
+ /**
35
+ * A Switch is a binary toggle control that represents on/off states.
36
+ * Built on top of `@radix-ui/react-switch` for full accessibility support
37
+ * including keyboard navigation, ARIA attributes, and screen reader announcements.
38
+ */
39
+ export declare const Switch: import("react").ForwardRefExoticComponent<SwitchProps & import("react").RefAttributes<HTMLButtonElement>>;
40
+ export type SwitchRootProps = ComponentPropsWithoutRef<typeof RadixSwitch.Root>;
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Bhcmstd2ViLXN3aXRjaC5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9pbmRleC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
6
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
7
+ var react$1 = require('@emotion/react');
8
+ var RadixSwitch = require('@radix-ui/react-switch');
9
+ var box = require('@spark-web/box');
10
+ var controlLabel = require('@spark-web/control-label');
11
+ var field = require('@spark-web/field');
12
+ var stack = require('@spark-web/stack');
13
+ var theme = require('@spark-web/theme');
14
+ var internal = require('@spark-web/utils/internal');
15
+ var react = require('react');
16
+ var jsxRuntime = require('@emotion/react/jsx-runtime');
17
+
18
+ function _interopNamespace(e) {
19
+ if (e && e.__esModule) return e;
20
+ var n = Object.create(null);
21
+ if (e) {
22
+ Object.keys(e).forEach(function (k) {
23
+ if (k !== 'default') {
24
+ var d = Object.getOwnPropertyDescriptor(e, k);
25
+ Object.defineProperty(n, k, d.get ? d : {
26
+ enumerable: true,
27
+ get: function () { return e[k]; }
28
+ });
29
+ }
30
+ });
31
+ }
32
+ n["default"] = e;
33
+ return Object.freeze(n);
34
+ }
35
+
36
+ var RadixSwitch__namespace = /*#__PURE__*/_interopNamespace(RadixSwitch);
37
+
38
+ var _excluded = ["children", "data", "disabled", "id", "message", "name", "onCheckedChange", "required", "size", "tone", "value"];
39
+ /**
40
+ * A Switch is a binary toggle control that represents on/off states.
41
+ * Built on top of `@radix-ui/react-switch` for full accessibility support
42
+ * including keyboard navigation, ARIA attributes, and screen reader announcements.
43
+ */
44
+ var Switch = /*#__PURE__*/react.forwardRef(function Switch(_ref, forwardedRef) {
45
+ var children = _ref.children,
46
+ data = _ref.data,
47
+ _ref$disabled = _ref.disabled,
48
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
49
+ idProp = _ref.id,
50
+ message = _ref.message,
51
+ name = _ref.name,
52
+ onCheckedChange = _ref.onCheckedChange,
53
+ required = _ref.required,
54
+ _ref$size = _ref.size,
55
+ size = _ref$size === void 0 ? 'small' : _ref$size,
56
+ _ref$tone = _ref.tone,
57
+ tone = _ref$tone === void 0 ? 'neutral' : _ref$tone,
58
+ value = _ref.value,
59
+ radixProps = _objectWithoutProperties(_ref, _excluded);
60
+ var _useFieldIds = field.useFieldIds(idProp),
61
+ inputId = _useFieldIds.inputId,
62
+ messageId = _useFieldIds.messageId;
63
+ var theme$1 = theme.useTheme();
64
+ var _useSwitchStyles = useSwitchStyles(size, theme$1),
65
+ trackStyles = _useSwitchStyles.trackStyles,
66
+ thumbStyles = _useSwitchStyles.thumbStyles;
67
+ return jsxRuntime.jsxs(stack.Stack, {
68
+ gap: "small",
69
+ position: "relative",
70
+ children: [jsxRuntime.jsx(controlLabel.ControlLabel, {
71
+ control: jsxRuntime.jsx(box.Box, {
72
+ display: "flex",
73
+ alignItems: "center",
74
+ flexShrink: 0,
75
+ children: jsxRuntime.jsx(RadixSwitch__namespace.Root, _objectSpread(_objectSpread(_objectSpread({}, radixProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
76
+ "aria-describedby": message ? messageId : undefined,
77
+ ref: forwardedRef,
78
+ disabled: disabled,
79
+ id: inputId,
80
+ name: name,
81
+ onCheckedChange: onCheckedChange,
82
+ required: required,
83
+ value: value,
84
+ css: react$1.css(trackStyles),
85
+ children: jsxRuntime.jsx(RadixSwitch__namespace.Thumb, {
86
+ css: react$1.css(thumbStyles)
87
+ })
88
+ }))
89
+ }),
90
+ disabled: disabled,
91
+ htmlFor: inputId,
92
+ size: size,
93
+ alignItems: "center",
94
+ children: children
95
+ }), message && jsxRuntime.jsx(field.FieldMessage, {
96
+ tone: tone,
97
+ id: messageId,
98
+ message: message
99
+ })]
100
+ });
101
+ });
102
+ Switch.displayName = 'Switch';
103
+
104
+ // ─── Styles ──────────────────────────────────────────────────────────────────
105
+
106
+ var trackDimensions = {
107
+ small: {
108
+ width: 36,
109
+ height: 20,
110
+ thumbSize: 16,
111
+ thumbOffset: 2
112
+ },
113
+ medium: {
114
+ width: 44,
115
+ height: 24,
116
+ thumbSize: 20,
117
+ thumbOffset: 2
118
+ }
119
+ };
120
+ function useSwitchStyles(size, theme) {
121
+ var _trackDimensions$size = trackDimensions[size],
122
+ width = _trackDimensions$size.width,
123
+ height = _trackDimensions$size.height,
124
+ thumbSize = _trackDimensions$size.thumbSize,
125
+ thumbOffset = _trackDimensions$size.thumbOffset;
126
+ var transition = {
127
+ transitionProperty: 'background-color, transform',
128
+ transitionTimingFunction: theme.animation.standard.easing,
129
+ transitionDuration: "".concat(theme.animation.standard.duration, "ms")
130
+ };
131
+ var trackStyles = _objectSpread(_objectSpread({
132
+ // Reset button styles
133
+ appearance: 'none',
134
+ border: 'none',
135
+ padding: 0,
136
+ cursor: 'pointer',
137
+ // Track shape
138
+ width: width,
139
+ height: height,
140
+ borderRadius: height / 2,
141
+ flexShrink: 0,
142
+ position: 'relative',
143
+ display: 'inline-flex',
144
+ alignItems: 'center',
145
+ // Unchecked colour
146
+ backgroundColor: theme.color.background.muted
147
+ }, transition), {}, {
148
+ // Checked state (Radix sets data-state="checked")
149
+ '&[data-state="checked"]': {
150
+ backgroundColor: theme.color.background.primary
151
+ },
152
+ // Focus ring
153
+ '&:focus-visible': {
154
+ outline: "2px solid ".concat(theme.color.foreground.primary),
155
+ outlineOffset: 2
156
+ },
157
+ // Hover — unchecked
158
+ '&:not([disabled]):not([data-state="checked"]):hover': {
159
+ backgroundColor: theme.color.background.disabled
160
+ },
161
+ // Hover — checked
162
+ '&:not([disabled])[data-state="checked"]:hover': {
163
+ backgroundColor: theme.color.background.primaryDark
164
+ },
165
+ // Disabled (Radix sets the disabled attribute)
166
+ '&[disabled]': {
167
+ cursor: 'not-allowed',
168
+ opacity: 0.5
169
+ }
170
+ });
171
+ var thumbStyles = _objectSpread(_objectSpread({
172
+ display: 'block',
173
+ width: thumbSize,
174
+ height: thumbSize,
175
+ borderRadius: '50%',
176
+ backgroundColor: '#ffffff',
177
+ boxShadow: theme.shadow.small,
178
+ // Unchecked position
179
+ transform: "translateX(".concat(thumbOffset, "px)")
180
+ }, transition), {}, {
181
+ // Checked position (Radix sets data-state="checked" on the Thumb too)
182
+ '&[data-state="checked"]': {
183
+ transform: "translateX(".concat(width - thumbSize - thumbOffset, "px)")
184
+ }
185
+ });
186
+ return {
187
+ trackStyles: trackStyles,
188
+ thumbStyles: thumbStyles
189
+ };
190
+ }
191
+
192
+ // Re-export for consumers who need the root prop types
193
+
194
+ exports.Switch = Switch;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./spark-web-switch.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./spark-web-switch.cjs.dev.js");
7
+ }
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
6
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
7
+ var react$1 = require('@emotion/react');
8
+ var RadixSwitch = require('@radix-ui/react-switch');
9
+ var box = require('@spark-web/box');
10
+ var controlLabel = require('@spark-web/control-label');
11
+ var field = require('@spark-web/field');
12
+ var stack = require('@spark-web/stack');
13
+ var theme = require('@spark-web/theme');
14
+ var internal = require('@spark-web/utils/internal');
15
+ var react = require('react');
16
+ var jsxRuntime = require('@emotion/react/jsx-runtime');
17
+
18
+ function _interopNamespace(e) {
19
+ if (e && e.__esModule) return e;
20
+ var n = Object.create(null);
21
+ if (e) {
22
+ Object.keys(e).forEach(function (k) {
23
+ if (k !== 'default') {
24
+ var d = Object.getOwnPropertyDescriptor(e, k);
25
+ Object.defineProperty(n, k, d.get ? d : {
26
+ enumerable: true,
27
+ get: function () { return e[k]; }
28
+ });
29
+ }
30
+ });
31
+ }
32
+ n["default"] = e;
33
+ return Object.freeze(n);
34
+ }
35
+
36
+ var RadixSwitch__namespace = /*#__PURE__*/_interopNamespace(RadixSwitch);
37
+
38
+ var _excluded = ["children", "data", "disabled", "id", "message", "name", "onCheckedChange", "required", "size", "tone", "value"];
39
+ /**
40
+ * A Switch is a binary toggle control that represents on/off states.
41
+ * Built on top of `@radix-ui/react-switch` for full accessibility support
42
+ * including keyboard navigation, ARIA attributes, and screen reader announcements.
43
+ */
44
+ var Switch = /*#__PURE__*/react.forwardRef(function Switch(_ref, forwardedRef) {
45
+ var children = _ref.children,
46
+ data = _ref.data,
47
+ _ref$disabled = _ref.disabled,
48
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
49
+ idProp = _ref.id,
50
+ message = _ref.message,
51
+ name = _ref.name,
52
+ onCheckedChange = _ref.onCheckedChange,
53
+ required = _ref.required,
54
+ _ref$size = _ref.size,
55
+ size = _ref$size === void 0 ? 'small' : _ref$size,
56
+ _ref$tone = _ref.tone,
57
+ tone = _ref$tone === void 0 ? 'neutral' : _ref$tone,
58
+ value = _ref.value,
59
+ radixProps = _objectWithoutProperties(_ref, _excluded);
60
+ var _useFieldIds = field.useFieldIds(idProp),
61
+ inputId = _useFieldIds.inputId,
62
+ messageId = _useFieldIds.messageId;
63
+ var theme$1 = theme.useTheme();
64
+ var _useSwitchStyles = useSwitchStyles(size, theme$1),
65
+ trackStyles = _useSwitchStyles.trackStyles,
66
+ thumbStyles = _useSwitchStyles.thumbStyles;
67
+ return jsxRuntime.jsxs(stack.Stack, {
68
+ gap: "small",
69
+ position: "relative",
70
+ children: [jsxRuntime.jsx(controlLabel.ControlLabel, {
71
+ control: jsxRuntime.jsx(box.Box, {
72
+ display: "flex",
73
+ alignItems: "center",
74
+ flexShrink: 0,
75
+ children: jsxRuntime.jsx(RadixSwitch__namespace.Root, _objectSpread(_objectSpread(_objectSpread({}, radixProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
76
+ "aria-describedby": message ? messageId : undefined,
77
+ ref: forwardedRef,
78
+ disabled: disabled,
79
+ id: inputId,
80
+ name: name,
81
+ onCheckedChange: onCheckedChange,
82
+ required: required,
83
+ value: value,
84
+ css: react$1.css(trackStyles),
85
+ children: jsxRuntime.jsx(RadixSwitch__namespace.Thumb, {
86
+ css: react$1.css(thumbStyles)
87
+ })
88
+ }))
89
+ }),
90
+ disabled: disabled,
91
+ htmlFor: inputId,
92
+ size: size,
93
+ alignItems: "center",
94
+ children: children
95
+ }), message && jsxRuntime.jsx(field.FieldMessage, {
96
+ tone: tone,
97
+ id: messageId,
98
+ message: message
99
+ })]
100
+ });
101
+ });
102
+ Switch.displayName = 'Switch';
103
+
104
+ // ─── Styles ──────────────────────────────────────────────────────────────────
105
+
106
+ var trackDimensions = {
107
+ small: {
108
+ width: 36,
109
+ height: 20,
110
+ thumbSize: 16,
111
+ thumbOffset: 2
112
+ },
113
+ medium: {
114
+ width: 44,
115
+ height: 24,
116
+ thumbSize: 20,
117
+ thumbOffset: 2
118
+ }
119
+ };
120
+ function useSwitchStyles(size, theme) {
121
+ var _trackDimensions$size = trackDimensions[size],
122
+ width = _trackDimensions$size.width,
123
+ height = _trackDimensions$size.height,
124
+ thumbSize = _trackDimensions$size.thumbSize,
125
+ thumbOffset = _trackDimensions$size.thumbOffset;
126
+ var transition = {
127
+ transitionProperty: 'background-color, transform',
128
+ transitionTimingFunction: theme.animation.standard.easing,
129
+ transitionDuration: "".concat(theme.animation.standard.duration, "ms")
130
+ };
131
+ var trackStyles = _objectSpread(_objectSpread({
132
+ // Reset button styles
133
+ appearance: 'none',
134
+ border: 'none',
135
+ padding: 0,
136
+ cursor: 'pointer',
137
+ // Track shape
138
+ width: width,
139
+ height: height,
140
+ borderRadius: height / 2,
141
+ flexShrink: 0,
142
+ position: 'relative',
143
+ display: 'inline-flex',
144
+ alignItems: 'center',
145
+ // Unchecked colour
146
+ backgroundColor: theme.color.background.muted
147
+ }, transition), {}, {
148
+ // Checked state (Radix sets data-state="checked")
149
+ '&[data-state="checked"]': {
150
+ backgroundColor: theme.color.background.primary
151
+ },
152
+ // Focus ring
153
+ '&:focus-visible': {
154
+ outline: "2px solid ".concat(theme.color.foreground.primary),
155
+ outlineOffset: 2
156
+ },
157
+ // Hover — unchecked
158
+ '&:not([disabled]):not([data-state="checked"]):hover': {
159
+ backgroundColor: theme.color.background.disabled
160
+ },
161
+ // Hover — checked
162
+ '&:not([disabled])[data-state="checked"]:hover': {
163
+ backgroundColor: theme.color.background.primaryDark
164
+ },
165
+ // Disabled (Radix sets the disabled attribute)
166
+ '&[disabled]': {
167
+ cursor: 'not-allowed',
168
+ opacity: 0.5
169
+ }
170
+ });
171
+ var thumbStyles = _objectSpread(_objectSpread({
172
+ display: 'block',
173
+ width: thumbSize,
174
+ height: thumbSize,
175
+ borderRadius: '50%',
176
+ backgroundColor: '#ffffff',
177
+ boxShadow: theme.shadow.small,
178
+ // Unchecked position
179
+ transform: "translateX(".concat(thumbOffset, "px)")
180
+ }, transition), {}, {
181
+ // Checked position (Radix sets data-state="checked" on the Thumb too)
182
+ '&[data-state="checked"]': {
183
+ transform: "translateX(".concat(width - thumbSize - thumbOffset, "px)")
184
+ }
185
+ });
186
+ return {
187
+ trackStyles: trackStyles,
188
+ thumbStyles: thumbStyles
189
+ };
190
+ }
191
+
192
+ // Re-export for consumers who need the root prop types
193
+
194
+ exports.Switch = Switch;
@@ -0,0 +1,170 @@
1
+ import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';
2
+ import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
3
+ import { css } from '@emotion/react';
4
+ import * as RadixSwitch from '@radix-ui/react-switch';
5
+ import { Box } from '@spark-web/box';
6
+ import { ControlLabel } from '@spark-web/control-label';
7
+ import { useFieldIds, FieldMessage } from '@spark-web/field';
8
+ import { Stack } from '@spark-web/stack';
9
+ import { useTheme } from '@spark-web/theme';
10
+ import { buildDataAttributes } from '@spark-web/utils/internal';
11
+ import { forwardRef } from 'react';
12
+ import { jsxs, jsx } from '@emotion/react/jsx-runtime';
13
+
14
+ var _excluded = ["children", "data", "disabled", "id", "message", "name", "onCheckedChange", "required", "size", "tone", "value"];
15
+ /**
16
+ * A Switch is a binary toggle control that represents on/off states.
17
+ * Built on top of `@radix-ui/react-switch` for full accessibility support
18
+ * including keyboard navigation, ARIA attributes, and screen reader announcements.
19
+ */
20
+ var Switch = /*#__PURE__*/forwardRef(function Switch(_ref, forwardedRef) {
21
+ var children = _ref.children,
22
+ data = _ref.data,
23
+ _ref$disabled = _ref.disabled,
24
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
25
+ idProp = _ref.id,
26
+ message = _ref.message,
27
+ name = _ref.name,
28
+ onCheckedChange = _ref.onCheckedChange,
29
+ required = _ref.required,
30
+ _ref$size = _ref.size,
31
+ size = _ref$size === void 0 ? 'small' : _ref$size,
32
+ _ref$tone = _ref.tone,
33
+ tone = _ref$tone === void 0 ? 'neutral' : _ref$tone,
34
+ value = _ref.value,
35
+ radixProps = _objectWithoutProperties(_ref, _excluded);
36
+ var _useFieldIds = useFieldIds(idProp),
37
+ inputId = _useFieldIds.inputId,
38
+ messageId = _useFieldIds.messageId;
39
+ var theme = useTheme();
40
+ var _useSwitchStyles = useSwitchStyles(size, theme),
41
+ trackStyles = _useSwitchStyles.trackStyles,
42
+ thumbStyles = _useSwitchStyles.thumbStyles;
43
+ return jsxs(Stack, {
44
+ gap: "small",
45
+ position: "relative",
46
+ children: [jsx(ControlLabel, {
47
+ control: jsx(Box, {
48
+ display: "flex",
49
+ alignItems: "center",
50
+ flexShrink: 0,
51
+ children: jsx(RadixSwitch.Root, _objectSpread(_objectSpread(_objectSpread({}, radixProps), data ? buildDataAttributes(data) : undefined), {}, {
52
+ "aria-describedby": message ? messageId : undefined,
53
+ ref: forwardedRef,
54
+ disabled: disabled,
55
+ id: inputId,
56
+ name: name,
57
+ onCheckedChange: onCheckedChange,
58
+ required: required,
59
+ value: value,
60
+ css: css(trackStyles),
61
+ children: jsx(RadixSwitch.Thumb, {
62
+ css: css(thumbStyles)
63
+ })
64
+ }))
65
+ }),
66
+ disabled: disabled,
67
+ htmlFor: inputId,
68
+ size: size,
69
+ alignItems: "center",
70
+ children: children
71
+ }), message && jsx(FieldMessage, {
72
+ tone: tone,
73
+ id: messageId,
74
+ message: message
75
+ })]
76
+ });
77
+ });
78
+ Switch.displayName = 'Switch';
79
+
80
+ // ─── Styles ──────────────────────────────────────────────────────────────────
81
+
82
+ var trackDimensions = {
83
+ small: {
84
+ width: 36,
85
+ height: 20,
86
+ thumbSize: 16,
87
+ thumbOffset: 2
88
+ },
89
+ medium: {
90
+ width: 44,
91
+ height: 24,
92
+ thumbSize: 20,
93
+ thumbOffset: 2
94
+ }
95
+ };
96
+ function useSwitchStyles(size, theme) {
97
+ var _trackDimensions$size = trackDimensions[size],
98
+ width = _trackDimensions$size.width,
99
+ height = _trackDimensions$size.height,
100
+ thumbSize = _trackDimensions$size.thumbSize,
101
+ thumbOffset = _trackDimensions$size.thumbOffset;
102
+ var transition = {
103
+ transitionProperty: 'background-color, transform',
104
+ transitionTimingFunction: theme.animation.standard.easing,
105
+ transitionDuration: "".concat(theme.animation.standard.duration, "ms")
106
+ };
107
+ var trackStyles = _objectSpread(_objectSpread({
108
+ // Reset button styles
109
+ appearance: 'none',
110
+ border: 'none',
111
+ padding: 0,
112
+ cursor: 'pointer',
113
+ // Track shape
114
+ width: width,
115
+ height: height,
116
+ borderRadius: height / 2,
117
+ flexShrink: 0,
118
+ position: 'relative',
119
+ display: 'inline-flex',
120
+ alignItems: 'center',
121
+ // Unchecked colour
122
+ backgroundColor: theme.color.background.muted
123
+ }, transition), {}, {
124
+ // Checked state (Radix sets data-state="checked")
125
+ '&[data-state="checked"]': {
126
+ backgroundColor: theme.color.background.primary
127
+ },
128
+ // Focus ring
129
+ '&:focus-visible': {
130
+ outline: "2px solid ".concat(theme.color.foreground.primary),
131
+ outlineOffset: 2
132
+ },
133
+ // Hover — unchecked
134
+ '&:not([disabled]):not([data-state="checked"]):hover': {
135
+ backgroundColor: theme.color.background.disabled
136
+ },
137
+ // Hover — checked
138
+ '&:not([disabled])[data-state="checked"]:hover': {
139
+ backgroundColor: theme.color.background.primaryDark
140
+ },
141
+ // Disabled (Radix sets the disabled attribute)
142
+ '&[disabled]': {
143
+ cursor: 'not-allowed',
144
+ opacity: 0.5
145
+ }
146
+ });
147
+ var thumbStyles = _objectSpread(_objectSpread({
148
+ display: 'block',
149
+ width: thumbSize,
150
+ height: thumbSize,
151
+ borderRadius: '50%',
152
+ backgroundColor: '#ffffff',
153
+ boxShadow: theme.shadow.small,
154
+ // Unchecked position
155
+ transform: "translateX(".concat(thumbOffset, "px)")
156
+ }, transition), {}, {
157
+ // Checked position (Radix sets data-state="checked" on the Thumb too)
158
+ '&[data-state="checked"]': {
159
+ transform: "translateX(".concat(width - thumbSize - thumbOffset, "px)")
160
+ }
161
+ });
162
+ return {
163
+ trackStyles: trackStyles,
164
+ thumbStyles: thumbStyles
165
+ };
166
+ }
167
+
168
+ // Re-export for consumers who need the root prop types
169
+
170
+ export { Switch };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@spark-web/switch",
3
+ "version": "5.0.1",
4
+ "homepage": "https://github.com/brighte-labs/spark-web#readme",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/brighte-labs/spark-web.git",
8
+ "directory": "packages/switch"
9
+ },
10
+ "license": "MIT",
11
+ "main": "dist/spark-web-switch.cjs.js",
12
+ "module": "dist/spark-web-switch.esm.js",
13
+ "exports": {
14
+ ".": {
15
+ "require": "./dist/spark-web-switch.cjs.js",
16
+ "import": "./dist/spark-web-switch.esm.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "CHANGELOG.md",
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "test": "jest"
26
+ },
27
+ "dependencies": {
28
+ "@babel/runtime": "^7.25.0",
29
+ "@emotion/react": "^11.14.0",
30
+ "@radix-ui/react-switch": "^1.2.6",
31
+ "@spark-web/box": "^6.0.0",
32
+ "@spark-web/control-label": "^5.1.0",
33
+ "@spark-web/field": "^5.3.0",
34
+ "@spark-web/stack": "^5.1.0",
35
+ "@spark-web/theme": "^5.13.0",
36
+ "@spark-web/utils": "^5.1.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^19.1.0",
40
+ "react": "^19.1.0"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=14"
47
+ }
48
+ }