@keyfunc/keybricks-ds 1.0.3 → 1.0.4
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/DEPLOY.md +3 -3
- package/INTEGRATION.md +6 -5
- package/_adherence.oxlintrc.json +21 -6
- package/components/_cards/radio.card.html +46 -0
- package/components/_cards/segmented-control.card.html +39 -0
- package/components/_cards/toggles.card.html +44 -55
- package/components/_shared/src-forms.js +1 -1
- package/components/_shared/src-tabs.js +1 -1
- package/components/_shared/view-source.js +9 -4
- package/components/forms/Radio.d.ts +28 -0
- package/components/forms/Radio.jsx +67 -0
- package/components/forms/Radio.vue +17 -8
- package/components/forms/RadioGroup.vue +22 -4
- package/components/forms/Switch.d.ts +32 -24
- package/components/forms/Switch.jsx +87 -35
- package/components/forms/Switch.vue +56 -11
- package/components/forms/forms.prompt.md +4 -1
- package/components/index.d.ts +1 -0
- package/components/index.js +2 -1
- package/components/tabs/SegmentedControl.d.ts +10 -3
- package/components/tabs/SegmentedControl.jsx +24 -9
- package/components/tabs/SegmentedControl.vue +19 -7
- package/components/tabs/tabs.prompt.md +3 -1
- package/package.json +1 -1
- package/readme.md +0 -1
- package/tokens/tokens.json +1 -1
package/DEPLOY.md
CHANGED
|
@@ -55,11 +55,11 @@ deploys the documentation site. Watch it under *Build → Pipelines*.
|
|
|
55
55
|
**5. Cut the first release.**
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
git tag v1.0.
|
|
58
|
+
git tag v1.0.4 && git push --tags
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
That tag runs `generated:check` → `version:check` → `npm publish`. Confirm under *Deploy →
|
|
62
|
-
Package Registry*: `@keyfunc/keybricks-ds` 1.0.
|
|
62
|
+
Package Registry*: `@keyfunc/keybricks-ds` 1.0.4.
|
|
63
63
|
|
|
64
64
|
**6. Hand developers two things:** the Pages URL, and the `.npmrc` snippet in §4 below.
|
|
65
65
|
|
|
@@ -156,7 +156,7 @@ GitLab injects `CI_JOB_TOKEN` — use that instead and no secret is stored anywh
|
|
|
156
156
|
**Install — straight from git** (no registry, no publish; works today)
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
|
-
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v1.0.
|
|
159
|
+
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v1.0.4"
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
Because the package has no build step, a git dependency needs no `prepare` script — npm
|
package/INTEGRATION.md
CHANGED
|
@@ -31,7 +31,7 @@ Not using a registry yet? A git dependency works with no publishing at all — t
|
|
|
31
31
|
has no build step, so npm can use the checkout as-is:
|
|
32
32
|
|
|
33
33
|
```json
|
|
34
|
-
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v1.0.
|
|
34
|
+
"@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v1.0.4"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
**Source-only package.** `.jsx` and `.vue` files are published as authored, so your
|
|
@@ -139,6 +139,7 @@ Shared non-component values live in plain modules: `forms/input-base.js` (`input
|
|
|
139
139
|
```js
|
|
140
140
|
// React
|
|
141
141
|
import { Input, Textarea } from '@keyfunc/keybricks-ds/components/forms/Input.jsx'
|
|
142
|
+
import { Radio, RadioGroup } from '@keyfunc/keybricks-ds/components/forms/Radio.jsx' // no longer inside Switch.jsx
|
|
142
143
|
// Vue
|
|
143
144
|
import Input from '@keyfunc/keybricks-ds/components/forms/Input.vue'
|
|
144
145
|
import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
@@ -168,8 +169,8 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
168
169
|
| `MultiSelect` | Checkbox dropdown; picks render as removable tags with `+n` overflow. |
|
|
169
170
|
| `SearchInput` | Search field with a leading glyph and a clear button. |
|
|
170
171
|
| `Checkbox` | Two sizes, plus an indeterminate state for parent rows. |
|
|
171
|
-
| `Radio` / `RadioGroup` | Single choice from a
|
|
172
|
-
| `Switch` | Immediate on/off
|
|
172
|
+
| `Radio` / `RadioGroup` | Single choice from 2–5 visible options (past five, use `Select`). `description` adds a muted second line; `row` lays out horizontally; `variant="cards"` renders bordered selectable cards for choices a user must weigh. Arrow keys move the selection. Vue: `v-model`. |
|
|
173
|
+
| `Switch` | Immediate on/off setting (no Save button). Three sizes; `size="lg"` when the knob carries `knobIcon` / `knobText` (one character). `insideOn`/`insideOff` for text in the pill, `labelOn`/`labelOff` for captions beside it (or `label` for a single settings caption). `tone="semantic"` is danger-off → success-on — reserve it for states with real consequence. `beforeChange(next)` returns a promise; the knob spins until it settles. Vue: `v-model:checked`. |
|
|
173
174
|
| `NumberStepper` | Numeric input with − / + buttons and min/max clamping. |
|
|
174
175
|
| `Slider` | Range input with a filled track, drag value bubble and optional min/max labels. |
|
|
175
176
|
| `DatePicker` | Text field plus a month-grid popover. Values are `YYYY-MM-DD` strings. |
|
|
@@ -206,7 +207,7 @@ import Textarea from '@keyfunc/keybricks-ds/components/forms/Textarea.vue'
|
|
|
206
207
|
| `Tabs` | Page-level underline tabs. |
|
|
207
208
|
| `PanelTabs` | Compact tabs inside a detail panel or drawer. |
|
|
208
209
|
| `PillTabs` | Filter/status tabs as pills. |
|
|
209
|
-
| `SegmentedControl` |
|
|
210
|
+
| `SegmentedControl` | Switches views of the same data, applies instantly — not a saved form value (`RadioGroup`) and not page navigation (`Tabs`). Two sizes, `icon` per option, `iconOnly`, per-option or whole-control `disabled`, `fullWidth`. ← → keyboard. |
|
|
210
211
|
| `ViewToggle` | List/grid icon toggle. |
|
|
211
212
|
|
|
212
213
|
### `feedback/` — status, overlays, progress
|
|
@@ -425,7 +426,7 @@ a migration note in the release. Token renames are major because your own code r
|
|
|
425
426
|
Pin a minor range and update deliberately:
|
|
426
427
|
|
|
427
428
|
```json
|
|
428
|
-
"@keyfunc/keybricks-ds": "~1.0.
|
|
429
|
+
"@keyfunc/keybricks-ds": "~1.0.4"
|
|
429
430
|
```
|
|
430
431
|
|
|
431
432
|
Release mechanics, the CI pipeline and the Pages site are documented in `DEPLOY.md`.
|
package/_adherence.oxlintrc.json
CHANGED
|
@@ -361,6 +361,14 @@
|
|
|
361
361
|
"selector": "JSXOpeningElement[name.name='RadarAxis'] > JSXAttribute > JSXIdentifier[name!=/^(?:label|max|key|ref|className|style|children)$/]",
|
|
362
362
|
"message": "<RadarAxis> doesn't accept that prop. Declared props: label, max."
|
|
363
363
|
},
|
|
364
|
+
{
|
|
365
|
+
"selector": "JSXOpeningElement[name.name='Radio'] > JSXAttribute > JSXIdentifier[name!=/^(?:checked|onChange|label|description|size|disabled|style|key|ref|className|style|children)$/]",
|
|
366
|
+
"message": "<Radio> doesn't accept that prop. Declared props: checked, onChange, label, description, size, disabled, style."
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"selector": "JSXOpeningElement[name.name='Radio'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md)$/]",
|
|
370
|
+
"message": "<Radio> size must be one of 'sm' | 'md'."
|
|
371
|
+
},
|
|
364
372
|
{
|
|
365
373
|
"selector": "JSXOpeningElement[name.name='Ribbon3DSeries'] > JSXAttribute > JSXIdentifier[name!=/^(?:name|values|color|key|ref|className|style|children)$/]",
|
|
366
374
|
"message": "<Ribbon3DSeries> doesn't accept that prop. Declared props: name, values, color."
|
|
@@ -382,8 +390,8 @@
|
|
|
382
390
|
"message": "<SearchInput> doesn't accept that prop. Declared props: placeholder, bare, value, onChange, disabled, style."
|
|
383
391
|
},
|
|
384
392
|
{
|
|
385
|
-
"selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute > JSXIdentifier[name!=/^(?:options|value|onChange|size|style|key|ref|className|style|children)$/]",
|
|
386
|
-
"message": "<SegmentedControl> doesn't accept that prop. Declared props: options, value, onChange, size, style."
|
|
393
|
+
"selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute > JSXIdentifier[name!=/^(?:options|value|onChange|size|iconOnly|disabled|fullWidth|style|key|ref|className|style|children)$/]",
|
|
394
|
+
"message": "<SegmentedControl> doesn't accept that prop. Declared props: options, value, onChange, size, iconOnly, disabled, fullWidth, style."
|
|
387
395
|
},
|
|
388
396
|
{
|
|
389
397
|
"selector": "JSXOpeningElement[name.name='SegmentedControl'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md)$/]",
|
|
@@ -446,12 +454,16 @@
|
|
|
446
454
|
"message": "<Surface3D> doesn't accept that prop. Declared props: values, width, height, yaw, pitch, zScale, wire, interactive, valueFormat, style."
|
|
447
455
|
},
|
|
448
456
|
{
|
|
449
|
-
"selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute > JSXIdentifier[name!=/^(?:checked|onChange|
|
|
450
|
-
"message": "<Switch> doesn't accept that prop. Declared props: checked, onChange,
|
|
457
|
+
"selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute > JSXIdentifier[name!=/^(?:checked|onChange|disabled|size|tone|onColor|offColor|knobIcon|knobText|insideOn|insideOff|inlinePrompt|label|labelOn|labelOff|loading|beforeChange|style|key|ref|className|style|children)$/]",
|
|
458
|
+
"message": "<Switch> doesn't accept that prop. Declared props: checked, onChange, disabled, size, tone, onColor, offColor, knobIcon, knobText, insideOn, insideOff, inlinePrompt, label, labelOn, labelOff, loading, beforeChange, style."
|
|
451
459
|
},
|
|
452
460
|
{
|
|
453
|
-
"selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:md|
|
|
454
|
-
"message": "<Switch> size must be one of 'md' | '
|
|
461
|
+
"selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute[name.name='size'] > Literal[value!=/^(?:sm|md|lg)$/]",
|
|
462
|
+
"message": "<Switch> size must be one of 'sm' | 'md' | 'lg'."
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"selector": "JSXOpeningElement[name.name='Switch'] > JSXAttribute[name.name='tone'] > Literal[value!=/^(?:primary|semantic)$/]",
|
|
466
|
+
"message": "<Switch> tone must be one of 'primary' | 'semantic'."
|
|
455
467
|
},
|
|
456
468
|
{
|
|
457
469
|
"selector": "JSXOpeningElement[name.name='Tabs'] > JSXAttribute > JSXIdentifier[name!=/^(?:tabs|active|onChange|style|key|ref|className|style|children)$/]",
|
|
@@ -708,6 +720,9 @@
|
|
|
708
720
|
"RadarAxis": {
|
|
709
721
|
"replaces": []
|
|
710
722
|
},
|
|
723
|
+
"Radio": {
|
|
724
|
+
"replaces": []
|
|
725
|
+
},
|
|
711
726
|
"Ribbon3DSeries": {
|
|
712
727
|
"replaces": []
|
|
713
728
|
},
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x620" name="Radio Button" subtitle="Single choice from a small set — md/sm · description · disabled · row · card variant · arrow-key navigation" -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css">
|
|
4
|
+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
5
|
+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|
6
|
+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
7
|
+
<script src="../../_ds_bundle.js"></script>
|
|
8
|
+
<script src="../_shared/view-source.js"></script>
|
|
9
|
+
<style>body{margin:0;padding:20px 24px;font-family:var(--font-sans);background:var(--bg-surface)}.sec{font-size:var(--text-2xs);font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--text-muted);padding:18px 0 6px;border-top:1px solid var(--border-subtle);margin-top:14px}.sec:first-child{border-top:none;margin-top:0;padding-top:0}.case{display:grid;grid-template-columns:170px 1fr;align-items:start;gap:20px;padding:6px 0}.k{font-size:var(--text-xs);color:var(--text-secondary);font-family:var(--font-mono);padding-top:2px}.v{display:flex;align-items:flex-start;gap:24px;flex-wrap:wrap}.note{font-size:var(--text-xs);line-height:17px;color:var(--text-secondary);max-width:60ch;padding-top:6px}a{color:var(--link)}a:hover{color:var(--link-hover)}</style>
|
|
10
|
+
</head><body><div id="root"></div>
|
|
11
|
+
<script type="text/babel">
|
|
12
|
+
const { Radio, RadioGroup } = window.KeybricksDesignSystem_71231a;
|
|
13
|
+
const Case = ({ k, children }) => <div className="case"><span className="k">{k}</span><div className="v">{children}</div></div>;
|
|
14
|
+
function Sheet() {
|
|
15
|
+
const [a, setA] = React.useState("Overwrite"), [b, setB] = React.useState("Ascending"), [c, setC] = React.useState("weekly"), [d, setD] = React.useState("merge"), [e, setE] = React.useState("USD"), [s1, setS1] = React.useState(true), [s2, setS2] = React.useState(true), [s3, setS3] = React.useState(false);
|
|
16
|
+
return <div>
|
|
17
|
+
<div className="sec">Single radio</div>
|
|
18
|
+
<Case k="md · sm"><Radio checked={s1} onChange={() => setS1(true)} label="Selected" /><Radio checked={!s1} onChange={() => setS1(false)} label="Unselected" /><Radio size="sm" checked={s2} onChange={() => setS2(true)} label="Small" /><Radio size="sm" checked={!s2} onChange={() => setS2(false)} label="Small" /></Case>
|
|
19
|
+
<Case k="disabled"><Radio checked disabled label="Locked on" /><Radio disabled label="Locked off" /></Case>
|
|
20
|
+
<Case k="description"><Radio checked={!s3} onChange={() => setS3(false)} label="Weekly digest" description="One summary every Monday morning" /><Radio checked={s3} onChange={() => setS3(true)} label="Daily" description="Every morning at 08:00" /></Case>
|
|
21
|
+
|
|
22
|
+
<div className="sec">RadioGroup — vertical (default)</div>
|
|
23
|
+
<Case k='options: string[]'><RadioGroup options={["New", "Overwrite", "Skip"]} value={a} onChange={setA} /></Case>
|
|
24
|
+
<Case k="with descriptions"><RadioGroup value={c} onChange={setC} options={[
|
|
25
|
+
{ label: "Weekly digest", value: "weekly", description: "One summary every Monday" },
|
|
26
|
+
{ label: "Daily", value: "daily", description: "Every morning at 08:00" },
|
|
27
|
+
{ label: "Never", value: "never", description: "Only in-app notifications", disabled: true }
|
|
28
|
+
]} /></Case>
|
|
29
|
+
|
|
30
|
+
<div className="sec">RadioGroup — row · sm</div>
|
|
31
|
+
<Case k="row"><RadioGroup row options={["USD", "EUR", "CNY", "JPY"]} value={e} onChange={setE} /></Case>
|
|
32
|
+
<Case k='row · size="sm"'><RadioGroup row size="sm" options={["Ascending", "Descending"]} value={b} onChange={setB} /></Case>
|
|
33
|
+
<Case k="disabled group"><RadioGroup row disabled options={["Draft", "Published"]} value="Draft" /></Case>
|
|
34
|
+
|
|
35
|
+
<div className="sec">RadioGroup — card variant</div>
|
|
36
|
+
<Case k='variant="cards" row'><RadioGroup variant="cards" row value={d} onChange={setD} style={{ width: 460 }} options={[
|
|
37
|
+
{ label: "Merge", value: "merge", description: "Keep target values, add new keys" },
|
|
38
|
+
{ label: "Overwrite", value: "overwrite", description: "Target takes source values" },
|
|
39
|
+
{ label: "Skip", value: "skip", description: "Leave conflicts untouched", disabled: true }
|
|
40
|
+
]} /></Case>
|
|
41
|
+
<div className="note">A radio group is the right control for 2–5 mutually exclusive options that should all stay visible. Past five, use <code>Select</code>. Arrow keys move the selection; Space/Enter selects the focused option. Use the card variant when each choice needs a description a user must weigh — migration strategies, plan tiers — never for simple settings.</div>
|
|
42
|
+
</div>;
|
|
43
|
+
}
|
|
44
|
+
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|
|
45
|
+
if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"Radio.jsx","path":"../forms/Radio.jsx"},{"name":"RadioGroup.vue","path":"../forms/RadioGroup.vue","vueOnly":true}]); }, 0);
|
|
46
|
+
</script></body></html>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x520" name="Segmented Control" subtitle="Mutually exclusive views in one inset track — md/sm · icons · icon-only · disabled option · full width · ViewToggle" -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css">
|
|
4
|
+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
5
|
+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|
6
|
+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
7
|
+
<script src="../../_ds_bundle.js"></script>
|
|
8
|
+
<script src="../_shared/view-source.js"></script>
|
|
9
|
+
<style>body{margin:0;padding:20px 24px;font-family:var(--font-sans);background:var(--bg-surface)}.sec{font-size:var(--text-2xs);font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--text-muted);padding:18px 0 6px;border-top:1px solid var(--border-subtle);margin-top:14px}.sec:first-child{border-top:none;margin-top:0;padding-top:0}.case{display:grid;grid-template-columns:170px 1fr;align-items:center;gap:20px;min-height:36px;padding:5px 0}.k{font-size:var(--text-xs);color:var(--text-secondary);font-family:var(--font-mono)}.v{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.note{font-size:var(--text-xs);line-height:17px;color:var(--text-secondary);max-width:60ch;padding-top:6px}a{color:var(--link)}a:hover{color:var(--link-hover)}</style>
|
|
10
|
+
</head><body><div id="root"></div>
|
|
11
|
+
<script type="text/babel">
|
|
12
|
+
const { SegmentedControl, ViewToggle } = window.KeybricksDesignSystem_71231a;
|
|
13
|
+
const Case = ({ k, children }) => <div className="case"><span className="k">{k}</span><div className="v">{children}</div></div>;
|
|
14
|
+
function Sheet() {
|
|
15
|
+
const [a, setA] = React.useState("Grid"), [b, setB] = React.useState("Last 30 days"), [c, setC] = React.useState("Table"), [d, setD] = React.useState("Actual"), [e, setE] = React.useState("Month"), [f, setF] = React.useState("Overview"), [v, setV] = React.useState("grid");
|
|
16
|
+
return <div>
|
|
17
|
+
<div className="sec">Sizes</div>
|
|
18
|
+
<Case k="md"><SegmentedControl options={["Actual", "Budget", "Variance"]} value={d} onChange={setD} /></Case>
|
|
19
|
+
<Case k="sm"><SegmentedControl size="sm" options={["Last 7 days", "Last 30 days", "Quarter"]} value={b} onChange={setB} /></Case>
|
|
20
|
+
|
|
21
|
+
<div className="sec">With icons</div>
|
|
22
|
+
<Case k="icon + label"><SegmentedControl options={[{ label: "List", icon: "list" }, { label: "Grid", icon: "layout-grid" }]} value={a} onChange={setA} /></Case>
|
|
23
|
+
<Case k="icon only · aria label"><SegmentedControl options={[{ icon: "table", label: "Table" }, { icon: "chart-column", label: "Chart" }, { icon: "code", label: "JSON" }]} iconOnly value={c} onChange={setC} /></Case>
|
|
24
|
+
|
|
25
|
+
<div className="sec">States</div>
|
|
26
|
+
<Case k="disabled option"><SegmentedControl options={[{ label: "Month" }, { label: "Quarter" }, { label: "Year", disabled: true }]} value={e} onChange={setE} /></Case>
|
|
27
|
+
<Case k="disabled control"><SegmentedControl disabled options={["Actual", "Budget"]} value="Actual" /></Case>
|
|
28
|
+
|
|
29
|
+
<div className="sec">Layout</div>
|
|
30
|
+
<Case k="fullWidth"><SegmentedControl fullWidth options={["Overview", "Details", "History"]} value={f} onChange={setF} style={{ width: 400 }} /></Case>
|
|
31
|
+
|
|
32
|
+
<div className="sec">ViewToggle — the two-state shorthand</div>
|
|
33
|
+
<Case k="list / grid"><ViewToggle value={v} onChange={setV} /></Case>
|
|
34
|
+
<div className="note">Segmented control switches <em>views of the same data</em> and applies instantly — never for a form value that gets saved (that's <code>RadioGroup</code>) and never for navigation between pages (that's <code>Tabs</code>). 2–5 options, labels one or two words. <code>ViewToggle</code> is the fixed list/grid pair used in panel headers.</div>
|
|
35
|
+
</div>;
|
|
36
|
+
}
|
|
37
|
+
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|
|
38
|
+
if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"SegmentedControl.jsx","path":"../tabs/SegmentedControl.jsx"},{"name":"ViewToggle.jsx","path":"../tabs/ViewToggle.jsx"}]); }, 0);
|
|
39
|
+
</script></body></html>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Components" viewport="
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x1060" name="Toggle Switch" subtitle="Binary settings pill — sizes · semantic red→green · glyph knobs · inline text · captions · async loading" -->
|
|
2
2
|
<!DOCTYPE html>
|
|
3
3
|
<html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css">
|
|
4
4
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
@@ -6,63 +6,52 @@
|
|
|
6
6
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
7
7
|
<script src="../../_ds_bundle.js"></script>
|
|
8
8
|
<script src="../_shared/view-source.js"></script>
|
|
9
|
-
<style>body{margin:0;
|
|
10
|
-
|
|
9
|
+
<style>body{margin:0;padding:20px 24px;font-family:var(--font-sans);background:var(--bg-surface)}.sec{font-size:var(--text-2xs);font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--text-muted);padding:18px 0 6px;border-top:1px solid var(--border-subtle);margin-top:14px}.sec:first-child{border-top:none;margin-top:0;padding-top:0}.case{display:grid;grid-template-columns:230px 1fr;align-items:center;gap:20px;min-height:36px;padding:5px 0}.k{font-size:var(--text-xs);color:var(--text-secondary);font-family:var(--font-mono)}.v{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.cap{font-size:var(--text-xs);color:var(--text-muted)}a{color:var(--link)}a:hover{color:var(--link-hover)}</style>
|
|
10
|
+
</head><body><div id="root"></div>
|
|
11
11
|
<script type="text/babel">
|
|
12
|
-
const { Switch
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const [
|
|
17
|
-
const [s,
|
|
18
|
-
const [t,setT]=React.useState("Last 30 days");
|
|
19
|
-
const [v,setV]=React.useState("grid");
|
|
12
|
+
const { Switch } = window.KeybricksDesignSystem_71231a;
|
|
13
|
+
const wait = (ms, ok = true) => new Promise(r => setTimeout(() => r(ok), ms));
|
|
14
|
+
const Case = ({ k, children }) => <div className="case"><span className="k">{k}</span><div className="v">{children}</div></div>;
|
|
15
|
+
function Sheet() {
|
|
16
|
+
const [v, set] = React.useState({});
|
|
17
|
+
const b = (k, d = false) => ({ checked: v[k] ?? d, onChange: n => set(s => ({ ...s, [k]: n })) });
|
|
20
18
|
return <div>
|
|
21
|
-
<div
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<div>
|
|
56
|
-
<span className="lbl">SegmentedControl — md / sm · ViewToggle</span>
|
|
57
|
-
<div style={{display:"flex",flexDirection:"column",gap:12,alignItems:"flex-start"}}>
|
|
58
|
-
<SegmentedControl options={[{label:"List",icon:"list"},{label:"Grid",icon:"layout-grid"}]} value={s} onChange={setS}/>
|
|
59
|
-
<SegmentedControl size="sm" options={["Last 7 days","Last 30 days","Quarter"]} value={t} onChange={setT}/>
|
|
60
|
-
<ViewToggle value={v} onChange={setV}/>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
19
|
+
<div className="sec">Sizes & states — sm · md · lg</div>
|
|
20
|
+
<Case k="on"><Switch size="sm" {...b("s1", true)} /><Switch {...b("s2", true)} /><Switch size="lg" {...b("s3", true)} /></Case>
|
|
21
|
+
<Case k="off"><Switch size="sm" {...b("s4")} /><Switch {...b("s5")} /><Switch size="lg" {...b("s6")} /></Case>
|
|
22
|
+
<Case k="disabled"><Switch size="sm" checked disabled /><Switch checked disabled /><Switch size="lg" checked disabled /></Case>
|
|
23
|
+
<Case k="label (settings row)"><Switch size="sm" label="Compact rows" {...b("s7", true)} /><Switch label="Debug Mode" {...b("s8", true)} /><Switch size="lg" label="Auto-run" {...b("s9", true)} /></Case>
|
|
24
|
+
|
|
25
|
+
<div className="sec">Semantic tone — danger off → success on</div>
|
|
26
|
+
<Case k='tone="semantic" · on'><Switch size="sm" tone="semantic" {...b("t1", true)} /><Switch tone="semantic" {...b("t2", true)} /><Switch size="lg" tone="semantic" {...b("t3", true)} /></Case>
|
|
27
|
+
<Case k="off"><Switch size="sm" tone="semantic" {...b("t4")} /><Switch tone="semantic" {...b("t5")} /><Switch size="lg" tone="semantic" {...b("t6")} /></Case>
|
|
28
|
+
|
|
29
|
+
<div className="sec">Glyph in the knob — md · lg only (sm knob is 12px, too small for a glyph)</div>
|
|
30
|
+
<Case k="knobIcon check"><Switch knobIcon="check" {...b("g1", true)} /><Switch size="lg" knobIcon="check" {...b("g2", true)} /></Case>
|
|
31
|
+
<Case k="knobIcon {check, x}"><Switch knobIcon={{ on: "check", off: "x" }} {...b("g3")} /><Switch size="lg" knobIcon={{ on: "check", off: "x" }} {...b("g4")} /></Case>
|
|
32
|
+
<Case k="knobIcon {sun, moon}"><Switch knobIcon={{ on: "sun", off: "moon" }} {...b("g5", true)} /><Switch size="lg" knobIcon={{ on: "sun", off: "moon" }} {...b("g6", true)} /></Case>
|
|
33
|
+
<Case k="knobText {Y, N}"><Switch knobText={{ on: "Y", off: "N" }} {...b("g7", true)} /><Switch size="lg" knobText={{ on: "Y", off: "N" }} {...b("g8", true)} /></Case>
|
|
34
|
+
<Case k="knobText {开, 关}"><Switch knobText={{ on: "开", off: "关" }} {...b("g9")} /><Switch size="lg" knobText={{ on: "开", off: "关" }} {...b("g10")} /></Case>
|
|
35
|
+
<Case k="semantic + knobIcon"><Switch tone="semantic" knobIcon={{ on: "check", off: "x" }} {...b("g11", true)} /><Switch size="lg" tone="semantic" knobIcon={{ on: "check", off: "x" }} {...b("g12", true)} /></Case>
|
|
36
|
+
|
|
37
|
+
<div className="sec">Text inside the pill — md · lg only</div>
|
|
38
|
+
<Case k="ON / OFF"><Switch insideOn="ON" insideOff="OFF" {...b("i1", true)} /><Switch size="lg" insideOn="ON" insideOff="OFF" {...b("i2", true)} /></Case>
|
|
39
|
+
<Case k="开 / 关"><Switch insideOn="开" insideOff="关" {...b("i3")} /><Switch size="lg" insideOn="开" insideOff="关" {...b("i4")} /></Case>
|
|
40
|
+
<Case k="semantic · Y / N"><Switch tone="semantic" insideOn="Y" insideOff="N" {...b("i5", true)} /><Switch size="lg" tone="semantic" insideOn="Y" insideOff="N" {...b("i6", true)} /></Case>
|
|
41
|
+
|
|
42
|
+
<div className="sec">Captions beside — sm · md · lg (active side reads heading)</div>
|
|
43
|
+
<Case k="labelOn"><Switch size="sm" labelOn="Enabled" {...b("l1", true)} /><Switch labelOn="Enabled" {...b("l2", true)} /><Switch size="lg" labelOn="Enabled" {...b("l3", true)} /></Case>
|
|
44
|
+
<Case k="labelOff + labelOn"><Switch size="sm" labelOff="Monthly" labelOn="Annual" {...b("l4")} /><Switch labelOff="Monthly" labelOn="Annual" {...b("l5")} /><Switch size="lg" labelOff="Monthly" labelOn="Annual" {...b("l6")} /></Case>
|
|
45
|
+
<Case k="semantic · Off / On"><Switch size="sm" tone="semantic" labelOff="Off" labelOn="On" {...b("l7", true)} /><Switch tone="semantic" labelOff="Off" labelOn="On" {...b("l8", true)} /><Switch size="lg" tone="semantic" labelOff="Off" labelOn="On" {...b("l9", true)} /></Case>
|
|
46
|
+
|
|
47
|
+
<div className="sec">Loading — sm · md · lg</div>
|
|
48
|
+
<Case k="loading · on"><Switch size="sm" checked loading /><Switch checked loading /><Switch size="lg" checked loading /></Case>
|
|
49
|
+
<Case k="loading · off"><Switch size="sm" loading /><Switch loading /><Switch size="lg" loading /></Case>
|
|
50
|
+
<Case k="semantic · loading"><Switch size="sm" tone="semantic" checked loading /><Switch tone="semantic" checked loading /><Switch size="lg" tone="semantic" checked loading /></Case>
|
|
51
|
+
<Case k="beforeChange → resolves 1.2s"><Switch size="sm" beforeChange={() => wait(1200)} {...b("a1", true)} /><Switch beforeChange={() => wait(1200)} {...b("a2", true)} /><Switch size="lg" knobIcon={{ on: "check", off: "x" }} beforeChange={() => wait(1200)} {...b("a3", true)} /></Case>
|
|
52
|
+
<Case k="beforeChange → rejects"><Switch size="sm" tone="semantic" beforeChange={() => wait(1200, false)} {...b("a4")} /><Switch tone="semantic" beforeChange={() => wait(1200, false)} {...b("a5")} /><Switch size="lg" tone="semantic" knobIcon={{ on: "check", off: "x" }} beforeChange={() => wait(1200, false)} {...b("a6")} /><span className="cap">stays put after the spinner</span></Case>
|
|
64
53
|
</div>;
|
|
65
54
|
}
|
|
66
55
|
ReactDOM.createRoot(document.getElementById("root")).render(<Sheet/>);
|
|
67
|
-
if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"Switch.jsx","path":"../forms/Switch.jsx"}
|
|
56
|
+
if (window.KBViewSource) setTimeout(function(){ KBViewSource.mount("#root", [{"name":"Switch.jsx","path":"../forms/Switch.jsx"}]); }, 0);
|
|
68
57
|
</script></body></html>
|