@scalewing/react 0.1.0 → 0.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +23 -2
  3. package/dist/components/Accordion.d.ts +8 -0
  4. package/dist/components/Accordion.js +14 -0
  5. package/dist/components/AppHeader.d.ts +5 -0
  6. package/dist/components/AppHeader.js +7 -0
  7. package/dist/components/Badge.d.ts +13 -0
  8. package/dist/components/Badge.js +7 -0
  9. package/dist/components/BarChart.d.ts +16 -0
  10. package/dist/components/BarChart.js +13 -0
  11. package/dist/components/Box.d.ts +4 -4
  12. package/dist/components/Box.js +1 -1
  13. package/dist/components/Button.d.ts +17 -0
  14. package/dist/components/Button.js +7 -0
  15. package/dist/components/Card.d.ts +2 -2
  16. package/dist/components/Card.js +2 -2
  17. package/dist/components/Dialog.d.ts +8 -0
  18. package/dist/components/Dialog.js +36 -0
  19. package/dist/components/Field.d.ts +11 -0
  20. package/dist/components/Field.js +6 -0
  21. package/dist/components/Nav.d.ts +3 -0
  22. package/dist/components/Nav.js +7 -0
  23. package/dist/components/SegmentedControl.d.ts +19 -0
  24. package/dist/components/SegmentedControl.js +28 -0
  25. package/dist/components/Select.d.ts +17 -0
  26. package/dist/components/Select.js +107 -0
  27. package/dist/components/Split.d.ts +8 -0
  28. package/dist/components/Split.js +117 -0
  29. package/dist/components/Table.d.ts +32 -0
  30. package/dist/components/Table.js +24 -0
  31. package/dist/components/Text.d.ts +3 -3
  32. package/dist/components/Text.js +2 -1
  33. package/dist/components/Toast.d.ts +10 -0
  34. package/dist/components/Toast.js +61 -0
  35. package/dist/index.d.ts +18 -0
  36. package/dist/index.js +13 -0
  37. package/dist/palette/amber.css +21 -0
  38. package/dist/palette/capri.css +21 -0
  39. package/dist/palette/cerulean.css +21 -0
  40. package/dist/palette/cornflower.css +21 -0
  41. package/dist/palette/fuchsia.css +21 -0
  42. package/dist/palette/harvest.css +31 -0
  43. package/dist/palette/ink.css +21 -0
  44. package/dist/palette/mocha.css +31 -0
  45. package/dist/palette/navy.css +21 -0
  46. package/dist/palette/postcard.css +31 -0
  47. package/dist/palette/raspberry.css +21 -0
  48. package/dist/palette/sky.css +21 -0
  49. package/dist/palette/soft-blue.css +21 -0
  50. package/dist/palette/sunburst.css +31 -0
  51. package/dist/palette/synthwave.css +26 -0
  52. package/dist/palette/tangerine.css +21 -0
  53. package/dist/select-list.d.ts +7 -0
  54. package/dist/select-list.js +24 -0
  55. package/dist/split-size.d.ts +18 -0
  56. package/dist/split-size.js +94 -0
  57. package/dist/styles.css +1228 -23
  58. package/dist/theme/ThemeProvider.d.ts +4 -3
  59. package/dist/theme/ThemeProvider.js +8 -3
  60. package/dist/toast-travel.d.ts +16 -0
  61. package/dist/toast-travel.js +42 -0
  62. package/package.json +7 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 247f158: Adds Accordion, a web disclosure on native details/summary, so in-flow panels can expand without a modal or an always-open Card.
8
+ - 247f158: Adds web page chrome: `AppHeader` (sticky glass), `Nav` (label-size link cluster, current page via `aria-current`), compact `Field` (`size="xs"`, `labelVisuallyHidden`), native `select` chevrons, and accent `:focus-visible` on text controls so selects do not keep the user-agent blue ring.
9
+ - 247f158: Adds Dialog, a web modal on the native `<dialog>` top layer, so info surfaces can leave nested page flow.
10
+ - 247f158: Adds a horizontal BarChart for labeled factor contributions, compact table density (`density="compact"`) with a selected-row marker, and compact Badge chicklets.
11
+ - 247f158: Default accent is electric indigo, and glass fill is derived from the merged accent so ThemeProvider overlays retint frost.
12
+
13
+ Adds opt-in density (`xs` controls, `data` type, `sw-tabular`) and web `Badge`, `SegmentedControl`, and `Table`. Pin the previous teal with `colors={{ accent: '#0B615E', onAccent: '#FFFFFF' }}` (dark: `#7EDAD6` / `#101214`).
14
+
15
+ - 247f158: Adds a named palette catalog so products pick a reviewed light/dark overlay instead of copying hex. Apply with ThemeProvider `palette`, `data-palette` on the generated canvas, or `import '@scalewing/react/palette/<id>.css'` after styles.css. Default remains indigo. `colors` still wins over a named palette and may be `{ light, dark }` so end users can switch scheme without the product swapping hex.
16
+ - 247f158: Adds Select, a web labeled listbox menu, so a compact choice can open with canvas glass instead of the operating system picker.
17
+ - 247f158: Adds Split, a web start pane with a labeled separator, so a column can be resized up to a max width and hidden when dragged too small.
18
+ - 247f158: Adds Toast, a web auto-dismiss confirmation on the popover layer that does not trap focus. Optional `anchor` and `target` send it from a press to a destination, then it vanishes. Ghost buttons with `aria-pressed="false"` use quiet opacity so a selected press stays full color.
19
+
20
+ ### Patch Changes
21
+
22
+ - 247f158: Adds Select action, a last listbox command that does not become the displayed value.
23
+ - Updated dependencies [247f158]
24
+ - Updated dependencies [247f158]
25
+ - Updated dependencies [247f158]
26
+ - Updated dependencies [247f158]
27
+ - Updated dependencies [247f158]
28
+ - Updated dependencies [247f158]
29
+ - Updated dependencies [247f158]
30
+ - Updated dependencies [247f158]
31
+ - Updated dependencies [247f158]
32
+ - Updated dependencies [247f158]
33
+ - Updated dependencies [247f158]
34
+ - Updated dependencies [247f158]
35
+ - @scalewing/tokens@0.3.0
36
+
37
+ ## 0.2.0
38
+
39
+ ### Minor Changes
40
+
41
+ - f35314a: Add Box `as="a"` for layout links and Field (label wrapping a native control, token gap) after fantasy-football. Press actions stay on Button; do not use Box as a button.
42
+ - f35314a: Add Button (primary, secondary, ghost, danger; sm and md) for DOM and React Native after FutMas and coach-platform requested the same control. Includes onAccent/onDanger, 44px md hit target, focus ring, and disabled opacity tokens.
43
+ - f35314a: Shift the default visual language to a quiet glass canvas: Apple-like neutrals and type, frosted Card, pill buttons, and generated document styles so links and native text controls are not user-agent chrome.
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [f35314a]
48
+ - Updated dependencies [f35314a]
49
+ - @scalewing/tokens@0.2.0
50
+
3
51
  ## 0.1.0
4
52
 
5
53
  ### Minor Changes
package/README.md CHANGED
@@ -4,13 +4,34 @@ DOM components plus the generated Scalewing stylesheet.
4
4
 
5
5
  ```ts
6
6
  import '@scalewing/react/styles.css';
7
- import { Card, Stack, Text, ThemeProvider } from '@scalewing/react';
7
+ import '@scalewing/react/palette/cerulean.css';
8
+ import {
9
+ Accordion,
10
+ BarChart,
11
+ Box,
12
+ Button,
13
+ Card,
14
+ Field,
15
+ Select,
16
+ Split,
17
+ Stack,
18
+ Text,
19
+ ThemeProvider,
20
+ } from '@scalewing/react';
21
+
22
+ <ThemeProvider colorScheme="system" palette="cerulean">
23
+ {children}
24
+ </ThemeProvider>
8
25
  ```
9
26
 
27
+ `colorScheme` is `"light"`, `"dark"`, or `"system"` so product users can switch. Each named palette already has a light pair and a dark pair. Brand overlays may be a flat `colors` map or `{ light, dark }` so both schemes are declared once.
28
+
10
29
  ```html
11
30
  <div class="sw-padding-top-4 sw-padding-x-4"></div>
12
31
  ```
13
32
 
14
- Import the CSS once at the application entry. Do not copy it into your source tree.
33
+ Import the CSS once at the application entry. Do not copy it into your source tree. Optional: import one `@scalewing/react/palette/<id>.css` file after it, or set `data-palette` on the `data-theme` node. React apps can set `palette` on `ThemeProvider` instead.
34
+
35
+ `Box as="a"` is a layout link. Use `Button` for press actions. `Field` wraps a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `Select` is a labeled listbox menu when the open list must match the canvas.
15
36
 
16
37
  React is a peer dependency. Rationale: the host app already owns the React runtime.
@@ -0,0 +1,8 @@
1
+ import { type DetailsHTMLAttributes, type ReactNode } from 'react';
2
+ export type AccordionProps = Omit<DetailsHTMLAttributes<HTMLDetailsElement>, 'onToggle' | 'open' | 'title' | 'children'> & {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ title: string;
6
+ children: ReactNode;
7
+ };
8
+ export declare function Accordion({ children, className, onOpenChange, open, title, ...rest }: AccordionProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cx } from '../class-names.js';
4
+ import { spacingClassNames } from '../spacing-classes.js';
5
+ import { Stack } from './Stack.js';
6
+ import { Text } from './Text.js';
7
+ export function Accordion({ children, className, onOpenChange, open, title, ...rest }) {
8
+ return (_jsxs("details", { ...rest, className: cx('sw-accordion', className), open: open, onToggle: (event) => {
9
+ const next = event.currentTarget.open;
10
+ if (next !== open) {
11
+ onOpenChange(next);
12
+ }
13
+ }, children: [_jsx("summary", { className: cx('sw-accordion-summary', ...spacingClassNames({ padding: 4 })), children: _jsx(Text, { as: "span", variant: "title", children: title }) }), _jsx(Stack, { gap: 4, padding: 4, paddingTop: 0, children: children })] }));
14
+ }
@@ -0,0 +1,5 @@
1
+ import { type BoxProps } from './Box.js';
2
+ export type AppHeaderProps = Omit<BoxProps, 'as'> & {
3
+ sticky?: boolean;
4
+ };
5
+ export declare function AppHeader({ className, padding, sticky, ...rest }: AppHeaderProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cx } from '../class-names.js';
4
+ import { Box } from './Box.js';
5
+ export function AppHeader({ className, padding = 4, sticky = true, ...rest }) {
6
+ return (_jsx(Box, { as: "header", className: cx('sw-app-header', sticky && 'sw-app-header-sticky', className), padding: padding, ...rest }));
7
+ }
@@ -0,0 +1,13 @@
1
+ import { type BadgeSize, type BadgeTone } from '@scalewing/tokens';
2
+ import { type HTMLAttributes, type ReactNode } from 'react';
3
+ export type { BadgeSize, BadgeTone };
4
+ export type BadgeProps = HTMLAttributes<HTMLSpanElement> & {
5
+ children: ReactNode;
6
+ size?: BadgeSize;
7
+ tone?: BadgeTone;
8
+ };
9
+ export declare const Badge: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLSpanElement> & {
10
+ children: ReactNode;
11
+ size?: BadgeSize;
12
+ tone?: BadgeTone;
13
+ } & import("react").RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { badgeClassNames, } from '@scalewing/tokens';
3
+ import { forwardRef } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ export const Badge = forwardRef(function Badge({ children, className, size = 'md', tone = 'neutral', ...rest }, ref) {
6
+ return (_jsx("span", { ref: ref, className: cx(...badgeClassNames(tone, size), className), ...rest, children: children }));
7
+ });
@@ -0,0 +1,16 @@
1
+ import { type HTMLAttributes } from 'react';
2
+ export type BarChartItem = {
3
+ label: string;
4
+ value: number;
5
+ valueLabel?: string;
6
+ };
7
+ export type BarChartProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
8
+ 'aria-label': string;
9
+ items: readonly BarChartItem[];
10
+ max?: number;
11
+ };
12
+ export declare const BarChart: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
13
+ 'aria-label': string;
14
+ items: readonly BarChartItem[];
15
+ max?: number;
16
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { barChartFillRatio, barChartScaleMax, formatBarChartValue, } from '@scalewing/tokens';
3
+ import { forwardRef } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ export const BarChart = forwardRef(function BarChart({ 'aria-label': ariaLabel, className, items, max, style, ...rest }, ref) {
6
+ const scaleMax = barChartScaleMax(items.map((item) => item.value), max);
7
+ return (_jsxs("div", { ref: ref, className: cx('sw-bar-chart', className), style: style, ...rest, children: [_jsx("ul", { "aria-label": ariaLabel, className: "sw-bar-chart-plot", children: items.map((item, index) => {
8
+ const ratio = barChartFillRatio(item.value, scaleMax);
9
+ return (_jsxs("li", { className: "sw-bar-chart-row", children: [_jsx("span", { className: "sw-bar-chart-label", children: item.label }), _jsx("span", { className: "sw-bar-chart-track", children: _jsx("span", { className: cx('sw-bar-chart-fill', item.value < 0 && 'sw-bar-chart-fill-negative'), style: {
10
+ '--sw-bar-fill': ratio,
11
+ } }) }), _jsx("span", { className: "sw-bar-chart-value", children: item.valueLabel ?? formatBarChartValue(item.value) })] }, `${item.label}-${index}`));
12
+ }) }), _jsxs("div", { "aria-hidden": "true", className: "sw-bar-chart-axis", children: [_jsx("span", {}), _jsxs("span", { className: "sw-bar-chart-axis-track", children: [_jsx("span", { children: "0" }), _jsx("span", { children: formatBarChartValue(scaleMax) })] }), _jsx("span", {})] })] }));
13
+ });
@@ -1,14 +1,14 @@
1
1
  import { type RadiusStep, type SemanticColorKey } from '@scalewing/tokens';
2
- import { type HTMLAttributes } from 'react';
2
+ import { type AnchorHTMLAttributes, type HTMLAttributes } from 'react';
3
3
  import { type SpacingProps } from '../spacing-classes.js';
4
- export type BoxElement = 'div' | 'section' | 'article' | 'header' | 'footer' | 'main' | 'nav' | 'aside' | 'span';
5
- export type BoxProps = SpacingProps & HTMLAttributes<HTMLElement> & {
4
+ export type BoxElement = 'div' | 'section' | 'article' | 'header' | 'footer' | 'main' | 'nav' | 'aside' | 'span' | 'a' | 'label';
5
+ export type BoxProps = SpacingProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'download' | 'href' | 'rel' | 'target'> & {
6
6
  as?: BoxElement;
7
7
  background?: Extract<SemanticColorKey, 'background' | 'surface'>;
8
8
  radius?: RadiusStep;
9
9
  border?: boolean;
10
10
  };
11
- export declare const Box: import("react").ForwardRefExoticComponent<SpacingProps & HTMLAttributes<HTMLElement> & {
11
+ export declare const Box: import("react").ForwardRefExoticComponent<SpacingProps & HTMLAttributes<HTMLElement> & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, "download" | "href" | "rel" | "target"> & {
12
12
  as?: BoxElement;
13
13
  background?: Extract<SemanticColorKey, "background" | "surface">;
14
14
  radius?: RadiusStep;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef } from 'react';
2
+ import { forwardRef, } from 'react';
3
3
  import { cx } from '../class-names.js';
4
4
  import { spacingClassNames } from '../spacing-classes.js';
5
5
  export const Box = forwardRef(function Box({ as = 'div', background, border, className, padding, paddingBottom, paddingLeft, paddingRight, paddingTop, paddingX, paddingY, radius, style, ...rest }, ref) {
@@ -0,0 +1,17 @@
1
+ import { type ButtonSize, type ButtonVariant } from '@scalewing/tokens';
2
+ import { type ButtonHTMLAttributes, type ReactNode } from 'react';
3
+ export type { ButtonSize, ButtonVariant };
4
+ export type ButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'type'> & {
5
+ children: ReactNode;
6
+ onPress: () => void;
7
+ size?: ButtonSize;
8
+ type?: 'button' | 'submit' | 'reset';
9
+ variant?: ButtonVariant;
10
+ };
11
+ export declare const Button: import("react").ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "onClick"> & {
12
+ children: ReactNode;
13
+ onPress: () => void;
14
+ size?: ButtonSize;
15
+ type?: "button" | "submit" | "reset";
16
+ variant?: ButtonVariant;
17
+ } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { buttonClassNames, } from '@scalewing/tokens';
3
+ import { forwardRef } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ export const Button = forwardRef(function Button({ children, className, disabled = false, onPress, size = 'md', type = 'button', variant = 'primary', ...rest }, ref) {
6
+ return (_jsx("button", { ref: ref, className: cx(...buttonClassNames({ size, variant }), className), disabled: disabled, type: type, ...rest, onClick: onPress, children: children }));
7
+ });
@@ -1,6 +1,6 @@
1
- import { type SpacingStep } from '@scalewing/tokens';
1
+ import { type SpacingStep, type CardVariant } from '@scalewing/tokens';
2
2
  import { type BoxProps } from './Box.js';
3
- export type CardVariant = 'outlined' | 'elevated';
3
+ export type { CardVariant };
4
4
  export type CardProps = Omit<BoxProps, 'as'> & {
5
5
  padding?: SpacingStep;
6
6
  variant?: CardVariant;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cx } from '../class-names.js';
3
3
  import { Box } from './Box.js';
4
- export function Card({ className, padding = 4, variant = 'outlined', ...rest }) {
5
- return (_jsx(Box, { as: "section", className: cx('sw-card', variant === 'elevated' && 'sw-card-elevated', className), padding: padding, ...rest }));
4
+ export function Card({ className, padding = 4, variant = 'glass', ...rest }) {
5
+ return (_jsx(Box, { as: "section", className: cx('sw-card', `sw-card-${variant}`, className), padding: padding, ...rest }));
6
6
  }
@@ -0,0 +1,8 @@
1
+ import { type DialogHTMLAttributes, type ReactNode } from 'react';
2
+ export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose' | 'open' | 'title' | 'children'> & {
3
+ open: boolean;
4
+ onClose: () => void;
5
+ title: string;
6
+ children: ReactNode;
7
+ };
8
+ export declare function Dialog({ children, className, onClose, open, title, ...rest }: DialogProps): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ import { spacingClassNames } from '../spacing-classes.js';
6
+ import { Stack } from './Stack.js';
7
+ import { Text } from './Text.js';
8
+ export function Dialog({ children, className, onClose, open, title, ...rest }) {
9
+ const nodeRef = useRef(null);
10
+ const titleId = useId();
11
+ useEffect(() => {
12
+ const node = nodeRef.current;
13
+ if (!node) {
14
+ return;
15
+ }
16
+ if (open) {
17
+ if (!node.open) {
18
+ node.showModal();
19
+ }
20
+ return;
21
+ }
22
+ if (node.open) {
23
+ node.close();
24
+ }
25
+ }, [open]);
26
+ return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-labelledby": titleId, className: cx('sw-dialog', ...spacingClassNames({ padding: 5 }), className), onClose: onClose, onPointerDown: (event) => {
27
+ const rect = event.currentTarget.getBoundingClientRect();
28
+ const inside = event.clientX >= rect.left &&
29
+ event.clientX <= rect.right &&
30
+ event.clientY >= rect.top &&
31
+ event.clientY <= rect.bottom;
32
+ if (!inside) {
33
+ onClose();
34
+ }
35
+ }, children: _jsxs(Stack, { gap: 4, children: [_jsx(Text, { id: titleId, variant: "title", children: title }), children] }) }));
36
+ }
@@ -0,0 +1,11 @@
1
+ import { type SpacingStep } from '@scalewing/tokens';
2
+ import { type ReactNode } from 'react';
3
+ export type FieldSize = 'xs' | 'md';
4
+ export type FieldProps = {
5
+ children: ReactNode;
6
+ gap?: SpacingStep;
7
+ label: string;
8
+ labelVisuallyHidden?: boolean;
9
+ size?: FieldSize;
10
+ };
11
+ export declare function Field({ children, gap, label, labelVisuallyHidden, size, }: FieldProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Stack } from './Stack.js';
3
+ import { Text } from './Text.js';
4
+ export function Field({ children, gap = 1, label, labelVisuallyHidden = false, size = 'md', }) {
5
+ return (_jsxs(Stack, { as: "label", className: size === 'xs' ? 'sw-field-xs' : undefined, gap: labelVisuallyHidden ? 0 : gap, children: [_jsx(Text, { as: "span", className: labelVisuallyHidden ? 'sw-sr-only' : undefined, variant: size === 'xs' ? 'caption' : 'label', children: label }), children] }));
6
+ }
@@ -0,0 +1,3 @@
1
+ import { type BoxProps } from './Box.js';
2
+ export type NavProps = Omit<BoxProps, 'as'>;
3
+ export declare function Nav({ className, ...rest }: NavProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { cx } from '../class-names.js';
4
+ import { Box } from './Box.js';
5
+ export function Nav({ className, ...rest }) {
6
+ return _jsx(Box, { as: "nav", className: cx('sw-nav', className), ...rest });
7
+ }
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from 'react';
2
+ export type SegmentedItem = {
3
+ id: string;
4
+ label: ReactNode;
5
+ };
6
+ type SegmentedLabel = {
7
+ 'aria-label': string;
8
+ 'aria-labelledby'?: never;
9
+ } | {
10
+ 'aria-label'?: never;
11
+ 'aria-labelledby': string;
12
+ };
13
+ export type SegmentedControlProps = SegmentedLabel & {
14
+ items: readonly SegmentedItem[];
15
+ onChange: (id: string) => void;
16
+ value: string;
17
+ };
18
+ export declare const SegmentedControl: import("react").ForwardRefExoticComponent<SegmentedControlProps & import("react").RefAttributes<HTMLDivElement>>;
19
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { cx } from '../class-names.js';
4
+ export const SegmentedControl = forwardRef(function SegmentedControl({ items, onChange, value, ...labelProps }, ref) {
5
+ function onKeyDown(event) {
6
+ if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') {
7
+ return;
8
+ }
9
+ const index = items.findIndex((item) => item.id === value);
10
+ if (index < 0) {
11
+ return;
12
+ }
13
+ event.preventDefault();
14
+ const delta = event.key === 'ArrowRight' ? 1 : -1;
15
+ const next = items[(index + delta + items.length) % items.length];
16
+ if (next) {
17
+ onChange(next.id);
18
+ }
19
+ }
20
+ return (_jsx("div", { ref: ref, className: "sw-segmented", onKeyDown: onKeyDown, role: "radiogroup", ...labelProps, children: items.map((item) => {
21
+ const selected = item.id === value;
22
+ return (_jsx("button", { "aria-checked": selected, className: cx('sw-segmented-item', selected && 'sw-segmented-item-selected'), onClick: () => {
23
+ if (!selected) {
24
+ onChange(item.id);
25
+ }
26
+ }, role: "radio", type: "button", children: item.label }, item.id));
27
+ }) }));
28
+ });
@@ -0,0 +1,17 @@
1
+ import { type SelectOption } from '../select-list.js';
2
+ import { type FieldSize } from './Field.js';
3
+ export type { SelectOption };
4
+ export type SelectAction = {
5
+ label: string;
6
+ onPress: () => void;
7
+ };
8
+ export type SelectProps = {
9
+ action?: SelectAction;
10
+ label: string;
11
+ labelVisuallyHidden?: boolean;
12
+ onChange: (value: string) => void;
13
+ options: readonly SelectOption[];
14
+ size?: FieldSize;
15
+ value: string;
16
+ };
17
+ export declare function Select({ action, label, labelVisuallyHidden, onChange, options, size, value, }: SelectProps): import("react").JSX.Element;
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState, } from 'react';
4
+ import { cx } from '../class-names.js';
5
+ import { selectIndexForKey, selectedSelectIndex, } from '../select-list.js';
6
+ import { Stack } from './Stack.js';
7
+ import { Text } from './Text.js';
8
+ function SelectListOption({ action = false, active, children, id, onCommit, onHighlight, selected, }) {
9
+ return (_jsx("div", { "aria-selected": selected, className: cx('sw-select-option', action && 'sw-select-action'), "data-active": active ? 'true' : undefined, id: id, onClick: onCommit, onPointerDown: (event) => {
10
+ event.preventDefault();
11
+ }, onPointerMove: onHighlight, role: "option", children: children }));
12
+ }
13
+ export function Select({ action, label, labelVisuallyHidden = false, onChange, options, size = 'md', value, }) {
14
+ const labelId = useId();
15
+ const triggerId = useId();
16
+ const listId = useId();
17
+ const optionIdPrefix = useId();
18
+ const rootRef = useRef(null);
19
+ const [open, setOpen] = useState(false);
20
+ const [highlight, setHighlight] = useState(0);
21
+ const selectedIndex = selectedSelectIndex(options, value);
22
+ const selected = options[selectedIndex];
23
+ const actionIndex = options.length;
24
+ const itemCount = action ? actionIndex + 1 : actionIndex;
25
+ useEffect(() => {
26
+ if (!open) {
27
+ return;
28
+ }
29
+ function onPointerDown(event) {
30
+ if (!rootRef.current?.contains(event.target)) {
31
+ setOpen(false);
32
+ }
33
+ }
34
+ document.addEventListener('pointerdown', onPointerDown);
35
+ return () => {
36
+ document.removeEventListener('pointerdown', onPointerDown);
37
+ };
38
+ }, [open]);
39
+ function openList() {
40
+ if (itemCount === 0) {
41
+ return;
42
+ }
43
+ setHighlight(selectedIndex);
44
+ setOpen(true);
45
+ }
46
+ function commit(index) {
47
+ if (action && index === actionIndex) {
48
+ action.onPress();
49
+ setOpen(false);
50
+ return;
51
+ }
52
+ const option = options[index];
53
+ if (option && option.value !== value) {
54
+ onChange(option.value);
55
+ }
56
+ setOpen(false);
57
+ }
58
+ function onTriggerKeyDown(event) {
59
+ if (event.key === 'Escape' && open) {
60
+ event.preventDefault();
61
+ event.stopPropagation();
62
+ setOpen(false);
63
+ return;
64
+ }
65
+ if (!open) {
66
+ if (event.key === 'ArrowDown' ||
67
+ event.key === 'ArrowUp' ||
68
+ event.key === 'Enter' ||
69
+ event.key === ' ') {
70
+ event.preventDefault();
71
+ openList();
72
+ }
73
+ return;
74
+ }
75
+ if (event.key === 'Enter' || event.key === ' ') {
76
+ event.preventDefault();
77
+ commit(highlight);
78
+ return;
79
+ }
80
+ const next = selectIndexForKey(event.key, highlight, itemCount);
81
+ if (next !== null) {
82
+ event.preventDefault();
83
+ setHighlight(next);
84
+ }
85
+ }
86
+ return (_jsx("div", { ref: rootRef, className: cx('sw-select', size === 'xs' && 'sw-select-xs'), children: _jsxs(Stack, { gap: labelVisuallyHidden ? 0 : 1, children: [_jsx(Text, { as: "label", className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: triggerId, id: labelId, variant: size === 'xs' ? 'caption' : 'label', children: label }), _jsxs("div", { className: "sw-select-control", children: [_jsx("button", { "aria-activedescendant": open ? `${optionIdPrefix}-${highlight}` : undefined, "aria-controls": open ? listId : undefined, "aria-expanded": open, "aria-haspopup": "listbox", "aria-labelledby": labelId, className: "sw-select-trigger", id: triggerId, onBlur: (event) => {
87
+ const next = event.relatedTarget;
88
+ if (next instanceof Node && rootRef.current?.contains(next)) {
89
+ return;
90
+ }
91
+ setOpen(false);
92
+ }, onClick: () => {
93
+ if (open) {
94
+ setOpen(false);
95
+ return;
96
+ }
97
+ openList();
98
+ }, onKeyDown: onTriggerKeyDown, role: "combobox", type: "button", children: selected?.label ?? '' }), open ? (_jsxs("div", { "aria-labelledby": labelId, className: "sw-select-list", id: listId, role: "listbox", children: [options.map((option, index) => (_jsx(SelectListOption, { active: index === highlight, id: `${optionIdPrefix}-${index}`, onCommit: () => {
99
+ commit(index);
100
+ }, onHighlight: () => {
101
+ setHighlight(index);
102
+ }, selected: option.value === value, children: option.label }, option.value))), action ? (_jsx(SelectListOption, { action: true, active: highlight === actionIndex, id: `${optionIdPrefix}-${actionIndex}`, onCommit: () => {
103
+ commit(actionIndex);
104
+ }, onHighlight: () => {
105
+ setHighlight(actionIndex);
106
+ }, selected: false, children: action.label })) : null] })) : null] })] }) }));
107
+ }
@@ -0,0 +1,8 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type SplitProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
3
+ children: ReactNode;
4
+ label: string;
5
+ collapsed?: boolean;
6
+ onCollapsedChange?: (collapsed: boolean) => void;
7
+ };
8
+ export declare function Split({ children, className, collapsed: collapsedProp, label, onCollapsedChange, ...rest }: SplitProps): import("react").JSX.Element;