@scalewing/react 1.2.0 → 1.3.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.
- package/CHANGELOG.md +7 -0
- package/dist/components/ButtonGroup.d.ts +14 -0
- package/dist/components/ButtonGroup.js +13 -0
- package/dist/components/SegmentedControl.d.ts +13 -2
- package/dist/components/SegmentedControl.js +6 -6
- package/dist/css/css-button.d.ts +1 -0
- package/dist/css/css-button.js +22 -1
- package/dist/css/css-data.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3ce39ea: Add `ButtonGroup`, the action row of a form or dialog: buttons on one line from the `md` breakpoint up, aligned `start`, `end` (default) or `between`; below `md` they stack full width in source order (generated `sw-button-group` and `sw-button-group-*`).
|
|
8
|
+
- 3e20a6e: Add `SegmentedControl` `disabled`: the group reports `aria-disabled`, every segment is a disabled button, arrow keys and clicks are ignored, and the track fades to `--sw-disabled-opacity` (generated `sw-segmented-disabled`). The recorded choice stays visible, for an identity that can no longer change; a disabled control may omit `onChange`.
|
|
9
|
+
|
|
3
10
|
## 1.2.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type BoxProps } from './Box.js';
|
|
2
|
+
export type ButtonGroupJustify = 'start' | 'end' | 'between';
|
|
3
|
+
export type ButtonGroupProps = BoxProps & {
|
|
4
|
+
/** Where the row sits from the `md` breakpoint up; below it every button stacks full width in source order. */
|
|
5
|
+
justify?: ButtonGroupJustify;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The action row of a form or dialog: buttons on one line, wrapping when
|
|
9
|
+
* they must, aligned to the end by default. On a phone the same buttons
|
|
10
|
+
* stack full width so each one is a whole-row target, in source order
|
|
11
|
+
* (put the primary action last, as the row already does). It is a `group`,
|
|
12
|
+
* so an `aria-label` names the row for assistive tech.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ButtonGroup({ className, justify, ...rest }: ButtonGroupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../class-names.js';
|
|
3
|
+
import { Box } from './Box.js';
|
|
4
|
+
/**
|
|
5
|
+
* The action row of a form or dialog: buttons on one line, wrapping when
|
|
6
|
+
* they must, aligned to the end by default. On a phone the same buttons
|
|
7
|
+
* stack full width so each one is a whole-row target, in source order
|
|
8
|
+
* (put the primary action last, as the row already does). It is a `group`,
|
|
9
|
+
* so an `aria-label` names the row for assistive tech.
|
|
10
|
+
*/
|
|
11
|
+
export function ButtonGroup({ className, justify = 'end', ...rest }) {
|
|
12
|
+
return (_jsx(Box, { className: cx('sw-button-group', `sw-button-group-${justify}`, className), role: "group", ...rest }));
|
|
13
|
+
}
|
|
@@ -17,9 +17,20 @@ type SegmentedLabel = {
|
|
|
17
17
|
* width in a Stack, the width of its widest label in an Inline.
|
|
18
18
|
*/
|
|
19
19
|
export type SegmentedControlVariant = 'compact' | 'filled';
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* A disabled control can never report a change, so it needs no `onChange`;
|
|
22
|
+
* a live one must have it.
|
|
23
|
+
*/
|
|
24
|
+
type SegmentedChange = {
|
|
25
|
+
disabled?: false;
|
|
22
26
|
onChange: (id: string) => void;
|
|
27
|
+
} | {
|
|
28
|
+
/** Keeps the current choice visible but inert, for an identity that can no longer change. */
|
|
29
|
+
disabled: true;
|
|
30
|
+
onChange?: (id: string) => void;
|
|
31
|
+
};
|
|
32
|
+
export type SegmentedControlProps = SegmentedLabel & SegmentedChange & {
|
|
33
|
+
items: readonly SegmentedItem[];
|
|
23
34
|
value: string;
|
|
24
35
|
variant?: SegmentedControlVariant;
|
|
25
36
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { cx } from '../class-names.js';
|
|
4
|
-
export const SegmentedControl = forwardRef(function SegmentedControl({ items, onChange, value, variant = 'compact', ...labelProps }, ref) {
|
|
4
|
+
export const SegmentedControl = forwardRef(function SegmentedControl({ disabled = false, items, onChange, value, variant = 'compact', ...labelProps }, ref) {
|
|
5
5
|
function onKeyDown(event) {
|
|
6
|
-
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
|
|
6
|
+
if (disabled || (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft')) {
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
const index = items.findIndex((item) => item.id === value);
|
|
@@ -14,14 +14,14 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ items, on
|
|
|
14
14
|
const delta = event.key === 'ArrowRight' ? 1 : -1;
|
|
15
15
|
const next = items[(index + delta + items.length) % items.length];
|
|
16
16
|
if (next) {
|
|
17
|
-
onChange(next.id);
|
|
17
|
+
onChange?.(next.id);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
return (_jsx("div", { ref: ref, className: cx('sw-segmented', variant === 'filled' && 'sw-segmented-filled'), onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
|
|
20
|
+
return (_jsx("div", { ref: ref, "aria-disabled": disabled || undefined, className: cx('sw-segmented', variant === 'filled' && 'sw-segmented-filled', disabled && 'sw-segmented-disabled'), onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
|
|
21
21
|
const selected = item.id === value;
|
|
22
|
-
return (_jsx("button", { "aria-checked": selected, className: cx('sw-segmented-item', selected && 'sw-segmented-item-selected'), onClick: () => {
|
|
22
|
+
return (_jsx("button", { "aria-checked": selected, className: cx('sw-segmented-item', selected && 'sw-segmented-item-selected'), disabled: disabled, onClick: () => {
|
|
23
23
|
if (!selected) {
|
|
24
|
-
onChange(item.id);
|
|
24
|
+
onChange?.(item.id);
|
|
25
25
|
}
|
|
26
26
|
}, role: "radio", type: "button", children: item.label }, item.id));
|
|
27
27
|
}) }));
|
package/dist/css/css-button.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare function buttonClassNames(options: {
|
|
|
4
4
|
variant: ButtonVariant;
|
|
5
5
|
}): string[];
|
|
6
6
|
export declare function cssButtonClasses(): string;
|
|
7
|
+
export declare const buttonGroupJustifies: readonly ["start", "end", "between"];
|
|
7
8
|
export declare function buttonClassCatalog(): string[];
|
package/dist/css/css-button.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buttonSizes, buttonVariants, controlScale, typographyVariants, } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
3
|
const label = typographyVariants.label;
|
|
3
4
|
export function buttonClassNames(options) {
|
|
4
5
|
return [
|
|
@@ -57,12 +58,32 @@ export function cssButtonClasses() {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
${variantRules()}
|
|
60
|
-
${sizeRules()}
|
|
61
|
+
${sizeRules()}
|
|
62
|
+
${groupRules()}`;
|
|
63
|
+
}
|
|
64
|
+
export const buttonGroupJustifies = ['start', 'end', 'between'];
|
|
65
|
+
/** The action row: one line from md up, full-width stacked buttons below it. */
|
|
66
|
+
function groupRules() {
|
|
67
|
+
const justify = {
|
|
68
|
+
start: 'flex-start',
|
|
69
|
+
end: 'flex-end',
|
|
70
|
+
between: 'space-between',
|
|
71
|
+
};
|
|
72
|
+
return [
|
|
73
|
+
`.sw-button-group { align-items: center; display: flex; flex-wrap: wrap; gap: var(--sw-space-2); min-width: 0; }`,
|
|
74
|
+
...buttonGroupJustifies.map((name) => `.sw-button-group-${name} { justify-content: ${justify[name]}; }`),
|
|
75
|
+
`@media ${breakpointQuery('below', 'md')} {
|
|
76
|
+
.sw-button-group { align-items: stretch; flex-direction: column; }
|
|
77
|
+
.sw-button-group > * { width: 100%; }
|
|
78
|
+
}`,
|
|
79
|
+
].join('\n');
|
|
61
80
|
}
|
|
62
81
|
export function buttonClassCatalog() {
|
|
63
82
|
return [
|
|
64
83
|
'sw-button',
|
|
65
84
|
...buttonVariants.map((variant) => `sw-button-${variant}`),
|
|
66
85
|
...buttonSizes.map((size) => `sw-button-${size}`),
|
|
86
|
+
'sw-button-group',
|
|
87
|
+
...buttonGroupJustifies.map((name) => `sw-button-group-${name}`),
|
|
67
88
|
];
|
|
68
89
|
}
|
package/dist/css/css-data.js
CHANGED
|
@@ -114,6 +114,14 @@ export function cssDataClasses() {
|
|
|
114
114
|
color: var(--sw-color-onAccent);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
.sw-segmented-disabled {
|
|
118
|
+
opacity: var(--sw-disabled-opacity);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.sw-segmented-item:disabled {
|
|
122
|
+
cursor: not-allowed;
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
.sw-table-wrap {
|
|
118
126
|
overflow: auto;
|
|
119
127
|
width: 100%;
|
|
@@ -203,6 +211,7 @@ export function dataClassCatalog() {
|
|
|
203
211
|
'sw-segmented-item',
|
|
204
212
|
'sw-segmented-item-selected',
|
|
205
213
|
'sw-segmented-filled',
|
|
214
|
+
'sw-segmented-disabled',
|
|
206
215
|
'sw-table-wrap',
|
|
207
216
|
'sw-table',
|
|
208
217
|
'sw-table-end',
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { Badge, type BadgeProps, type BadgeSize, type BadgeTone, badgeSizes, bad
|
|
|
13
13
|
export { BarChart, type BarChartItem, type BarChartProps, } from './components/BarChart.js';
|
|
14
14
|
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from './components/Button.js';
|
|
15
15
|
export { Box, type BoxElement, type BoxProps } from './components/Box.js';
|
|
16
|
+
export { ButtonGroup, type ButtonGroupJustify, type ButtonGroupProps, } from './components/ButtonGroup.js';
|
|
16
17
|
export { Card, type CardProps, type CardVariant } from './components/Card.js';
|
|
17
18
|
export { Dialog, type DialogProps, type DialogSize, } from './components/Dialog.js';
|
|
18
19
|
export { Toast } from './components/Toast.js';
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { Badge, badgeSizes, badgeTones, } from './components/Badge.js';
|
|
|
12
12
|
export { BarChart, } from './components/BarChart.js';
|
|
13
13
|
export { Button, } from './components/Button.js';
|
|
14
14
|
export { Box } from './components/Box.js';
|
|
15
|
+
export { ButtonGroup, } from './components/ButtonGroup.js';
|
|
15
16
|
export { Card } from './components/Card.js';
|
|
16
17
|
export { Dialog, } from './components/Dialog.js';
|
|
17
18
|
export { Toast } from './components/Toast.js';
|
package/dist/styles.css
CHANGED
|
@@ -776,6 +776,14 @@ body {
|
|
|
776
776
|
.sw-button-xs { min-height: 28px; padding-inline: 10px; }
|
|
777
777
|
.sw-button-sm { min-height: 32px; padding-inline: 12px; }
|
|
778
778
|
.sw-button-md { min-height: 44px; padding-inline: 16px; }
|
|
779
|
+
.sw-button-group { align-items: center; display: flex; flex-wrap: wrap; gap: var(--sw-space-2); min-width: 0; }
|
|
780
|
+
.sw-button-group-start { justify-content: flex-start; }
|
|
781
|
+
.sw-button-group-end { justify-content: flex-end; }
|
|
782
|
+
.sw-button-group-between { justify-content: space-between; }
|
|
783
|
+
@media not all and (min-width: 48rem) {
|
|
784
|
+
.sw-button-group { align-items: stretch; flex-direction: column; }
|
|
785
|
+
.sw-button-group > * { width: 100%; }
|
|
786
|
+
}
|
|
779
787
|
|
|
780
788
|
.sw-checkbox {
|
|
781
789
|
display: flex;
|
|
@@ -1124,6 +1132,14 @@ body {
|
|
|
1124
1132
|
color: var(--sw-color-onAccent);
|
|
1125
1133
|
}
|
|
1126
1134
|
|
|
1135
|
+
.sw-segmented-disabled {
|
|
1136
|
+
opacity: var(--sw-disabled-opacity);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.sw-segmented-item:disabled {
|
|
1140
|
+
cursor: not-allowed;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1127
1143
|
.sw-table-wrap {
|
|
1128
1144
|
overflow: auto;
|
|
1129
1145
|
width: 100%;
|