@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 +16 -0
- package/README.md +98 -0
- package/dist/declarations/src/index.d.ts +2 -0
- package/dist/declarations/src/switch.d.ts +40 -0
- package/dist/spark-web-switch.cjs.d.ts +2 -0
- package/dist/spark-web-switch.cjs.dev.js +194 -0
- package/dist/spark-web-switch.cjs.js +7 -0
- package/dist/spark-web-switch.cjs.prod.js +194 -0
- package/dist/spark-web-switch.esm.js +170 -0
- package/package.json +48 -0
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,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,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
|
+
}
|