@pastweb/components-core 2.0.0 → 2.2.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.
@@ -0,0 +1,158 @@
1
+ # Panel
2
+
3
+ Panel groups related content in a bordered, optionally rounded article with a
4
+ semantic heading, close action, and footer. Its rendering contract targets
5
+ WCAG 2.2 Level AA. It is a content grouping container, not a confirmation or
6
+ interruption panel.
7
+
8
+ ## When to use
9
+
10
+ Use Panel to visually group a self-contained section of page content. Keep
11
+ heading levels consistent with the surrounding document. Do not use it as a
12
+ modal dialog without adding the separate dialog focus and interaction contract.
13
+ Do not use it as a GOV.UK confirmation or interruption panel.
14
+
15
+ ## Props
16
+
17
+ | PropName | Type | Required | Default | Description |
18
+ |---|---|---:|---:|---|
19
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component, part, and state classes. |
20
+ | `ariaLabel` | `string` | no | `""` | Names the article when no title is supplied. Pass an empty string to omit the name. |
21
+ | `border` | `boolean` | no | `true` | Applies the `has-border` class. The public prop name remains `border`. |
22
+ | `borderRadius` | `boolean` | no | `true` | Applies the `has-border-radius` class. The public prop name remains `borderRadius`. |
23
+ | `className` | `string` | no | `""` | Adds classes to the root article. |
24
+ | `closeButton` | `boolean` | no | `true` | Displays the native close button. |
25
+ | `closeLabel` | `string` | no | `"Close panel"` | Names the close button. |
26
+ | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | no | `2` | Selects the native title heading level. |
27
+ | `id` | `string` | no | generated | Supplies the stable panel ID and title-ID base. Generated IDs use the `panel` prefix. Explicit IDs are stored as `_${id}`. |
28
+ | `title` | `string` | no | `""` | Supplies the default heading content. |
29
+ | `contentPadding` | `number \| string` | no | `0` | Sets inline padding on the content area. |
30
+ | `onClose` | `() => void` | no | noop | Reports close intent. |
31
+ | `rtl` | `boolean` | no | - | Reverses header content order. |
32
+
33
+ ## Mediator state
34
+
35
+ | Property | Type | Description |
36
+ |---|---|---|
37
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name when no title is present. Empty labels are omitted. |
38
+ | `heading` | `PanelHeading` | Native title tag from `headingLevel`, such as `h2`. |
39
+ | `id` | `string` | Article ID from an explicit `id` or a generated `panel` prefix. |
40
+ | `titleId` | `string \| undefined` | Title relationship ID `title${id}` when heading content is present; otherwise omitted. |
41
+
42
+ ## Slots
43
+
44
+ | SlotName | Description | Default content |
45
+ |---|---|---|
46
+ | default | Supplies panel body content. | Empty. |
47
+ | `header` | Replaces title and close content inside the semantic header. | Heading and optional close button. |
48
+ | `footer` | Supplies content wrapped in the semantic footer. | Empty. |
49
+
50
+ ## CSS classes
51
+
52
+ | Class | Applied when | Notes |
53
+ |---|---|---|
54
+ | `Panel` | Always | Root component class. |
55
+ | `has-border` | `border` is `true` | Optional article border. |
56
+ | `has-border-radius` | `borderRadius` is `true` | Optional article corner radius. |
57
+
58
+ ## CSS Variables
59
+
60
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
61
+ |---|---|---|---|---|---|---|
62
+ | `--pw_Panel_root_default_background-color` | `root` | `default` | `background-color` | `var(--pw_background-color)` | Panel background. | Web, Capacitor, NativeScript |
63
+ | `--pw_Panel_root_default_color` | `root` | `default` | `color` | `var(--pw_text_color)` | Panel foreground. | Web, Capacitor, NativeScript |
64
+ | `--pw_Panel_root_default_font-size` | `root` | `default` | `font-size` | `var(--pw_font-size)` | Body text size. | Web, Capacitor, NativeScript |
65
+ | `--pw_Panel_root_default_font-weight` | `root` | `default` | `font-weight` | `var(--pw_font-weight)` | Body text weight. | Web, Capacitor, NativeScript |
66
+ | `--pw_Panel_root_default_line-height` | `root` | `default` | `line-height` | `var(--pw_line-height)` | Body line height. | Web, Capacitor, NativeScript |
67
+ | `--pw_Panel_root_default_border-color` | `root` | `default` | `border-color` | `var(--pw_border-color)` | Optional border colour. | Web, Capacitor, NativeScript |
68
+ | `--pw_Panel_root_default_border-width` | `root` | `default` | `border-width` | `var(--pw_border-width)` | Optional border width. | Web, Capacitor, NativeScript |
69
+ | `--pw_Panel_root_default_border-style` | `root` | `default` | `border-style` | `var(--pw_border-style)` | Optional border style. | Web, Capacitor, NativeScript |
70
+ | `--pw_Panel_root_default_border-radius` | `root` | `default` | `border-radius` | `var(--pw_border-radius)` | Optional corner radius. | Web, Capacitor, NativeScript |
71
+ | `--pw_Panel_header_default_padding` | `header` | `default` | `padding` | `var(--pw_padding)` | Header spacing. | Web, Capacitor, NativeScript |
72
+ | `--pw_Panel_header_default_background-color` | `header` | `default` | `background-color` | `var(--pw_control_background-color)` | Header background. | Web, Capacitor, NativeScript |
73
+ | `--pw_Panel_header_default_border-bottom` | `header` | `default` | `border-bottom` | shared border | Header separator. | Web, Capacitor, NativeScript |
74
+ | `--pw_Panel_header_default_color` | `header` | `default` | `color` | `var(--pw_text_strong_color)` | Header foreground. | Web, Capacitor, NativeScript |
75
+ | `--pw_Panel_header_default_font-size` | `header` | `default` | `font-size` | `1em` | Heading text size. | Web, Capacitor, NativeScript |
76
+ | `--pw_Panel_header_default_font-weight` | `header` | `default` | `font-weight` | `var(--pw_bold_font-weight)` | Heading weight. | Web, Capacitor, NativeScript |
77
+ | `--pw_Panel_header_default_line-height` | `header` | `default` | `line-height` | `var(--pw_line-height)` | Heading line height. | Web, Capacitor, NativeScript |
78
+ | `--pw_Panel_close-button_default_background-color` | `close-button` | `default` | `background-color` | header background | Close-button background. | Web, Capacitor, NativeScript |
79
+ | `--pw_Panel_close-button_hover_background-color` | `close-button` | `hover` | `background-color` | header colour | Hover background. | Web, Capacitor |
80
+ | `--pw_Panel_close-button_default_min-width` | `close-button` | `default` | `min-width` | `1.563em` | Close-button width, matching Notification. | Web, Capacitor, NativeScript |
81
+ | `--pw_Panel_close-button_default_height` | `close-button` | `default` | `height` | `1.563em` | Close-button height, matching Notification. | Web, Capacitor, NativeScript |
82
+ | `--pw_Panel_close-button_default_transition-duration` | `close-button` | `default` | `transition-duration` | `0.5s` | Visual transition duration. | Web, Capacitor; NativeScript adapter |
83
+ | `--pw_Panel_close-bar_default_background-color` | `close-bar` | `default` | `background-color` | header colour | Close glyph colour. | Web, Capacitor, NativeScript |
84
+ | `--pw_Panel_close-bar_hover_background-color` | `close-bar` | `hover` | `background-color` | close-button background | Hover glyph colour. | Web, Capacitor |
85
+ | `--pw_Panel_footer_default_padding` | `footer` | `default` | `padding` | header padding | Footer spacing. | Web, Capacitor, NativeScript |
86
+ | `--pw_Panel_footer_default_background-color` | `footer` | `default` | `background-color` | header background | Footer background. | Web, Capacitor, NativeScript |
87
+ | `--pw_Panel_footer_default_border-top` | `footer` | `default` | `border-top` | header separator | Footer separator. | Web, Capacitor, NativeScript |
88
+
89
+ ```xts
90
+ import { useBeforeUnmount, useMediator, withDefaultProps } from '@pastweb/x';
91
+ import { panelMediator } from '@pastweb/components-core/Panel';
92
+ import { DEFAULT_PROPS } from './constants';
93
+
94
+ export function Panel(props: PanelProps) {
95
+ const p = withDefaultProps<PanelDefaultProps>(props, DEFAULT_PROPS);
96
+ const m = useMediator<PanelMediator>(panelMediator, p);
97
+ const s = m.state;
98
+ const Heading = s.heading;
99
+
100
+ useBeforeUnmount(m.useBeforeUnmount);
101
+
102
+ return (
103
+ <article
104
+ id={s.id}
105
+ class={p.cls('Panel', {
106
+ 'has-border': p.border,
107
+ 'has-border-radius': p.borderRadius,
108
+ }, p.className)}
109
+ aria-label={s.ariaLabel}
110
+ aria-labelledby={s.titleId}
111
+ >
112
+ <header class={p.cls('header', { rtl: p.rtl })}>
113
+ <slot name="header">
114
+ <Heading #if={p.title} id={s.titleId} class={p.cls('title')}>{p.title}</Heading>
115
+ <button #if={p.closeButton} type="button" class={p.cls('closeButton')} @click={m.onClose} aria-label={p.closeLabel}>
116
+ <span class={p.cls('closeBar')} aria-hidden="true" />
117
+ <span class={p.cls('closeBar')} aria-hidden="true" />
118
+ </button>
119
+ </slot>
120
+ </header>
121
+ <div class={p.cls('content')} #style={p.contentPadding ? { padding: p.contentPadding } : {}}>
122
+ <slot />
123
+ </div>
124
+ <slot name="footer" map={content => <footer class={p.cls('footer')}>{content}</footer>} />
125
+ </article>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ## Keyboard interaction
131
+
132
+ - The close action is a native button and is operable with `Enter` or `Space`.
133
+ - Body content is not interactive unless projected content supplies its own
134
+ controls.
135
+
136
+ ## Accessibility notes
137
+
138
+ - The default title is a native heading connected to the article through
139
+ `aria-labelledby` and the `title${id}` relationship ID.
140
+ - Without a title, `ariaLabel` can provide the article's accessible name. The
141
+ default is an empty string, which omits the name.
142
+ - The close action is a native button with a configurable name. Decorative close
143
+ bars are excluded from the accessibility tree.
144
+ - A custom header must preserve an appropriate heading and close-action name.
145
+ - Shared web styles match the Notification close control size, with visible
146
+ keyboard focus, forced-colours treatment, and reduced-motion behavior.
147
+
148
+ ## Known limitations
149
+
150
+ - Automated accessibility checks do not by themselves establish WCAG
151
+ conformance.
152
+
153
+ ## Standards
154
+
155
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
156
+ - WAI-ARIA landmark regions: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/
157
+ - GOV.UK Panel guidance: https://design-system.service.gov.uk/components/panel/
158
+ - GOV.UK headings guidance: https://design-system.service.gov.uk/styles/headings/
@@ -0,0 +1,124 @@
1
+ # Placeholder
2
+
3
+ Placeholder renders a non-interactive shape that stands in for content that has
4
+ not loaded yet. Its rendering contract targets WCAG 2.2 Level AA.
5
+
6
+ ## When to use
7
+
8
+ Use Placeholder to reserve layout while content loads. Keep placeholders
9
+ decorative when several shapes compose one skeleton, and announce loading once
10
+ on a parent region or with a single non-decorative Placeholder. Do not rely on
11
+ the pulse animation as the only loading signal. Use Spinner for a compact
12
+ indeterminate status, and ProgressBar when completion can be calculated.
13
+
14
+ ## Props
15
+
16
+ | PropName | Type | Required | Default | Description |
17
+ |---|---|---:|---:|---|
18
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and state classes. |
19
+ | `ariaLabel` | `string` | no | `"Loading"` | Names the status when the placeholder is not decorative. Pass an empty string to omit the name. |
20
+ | `className` | `string` | no | `""` | Adds classes to the root, including shape utilities. |
21
+ | `decorative` | `boolean` | no | `true` | Removes status semantics, hides the shape from the accessibility tree, and applies `is-decorative`. |
22
+
23
+ ## Mediator state
24
+
25
+ | Property | Type | Description |
26
+ |---|---|---|
27
+ | `ariaHidden` | `true \| undefined` | Is `true` when the placeholder is decorative; otherwise omitted. Renderers use this value directly for `aria-hidden`. |
28
+ | `ariaLabel` | `string \| undefined` | Normalized status name. Decorative and empty labels are omitted. |
29
+ | `ariaLive` | `'polite' \| undefined` | Is `polite` unless decorative. |
30
+ | `role` | `'status' \| undefined` | Is `status` unless decorative. |
31
+
32
+ ## Utility classes
33
+
34
+ Apply these classes through `className`. The default shape is a full-width bar
35
+ at Button height using the same `--pw_border-radius` token as Button.
36
+ `is-circle`, `is-square`, and `is-block` replace that size. `is-rounded` and
37
+ `is-pill` change the corner radius and can combine with the default, square, or
38
+ block shapes. A circle keeps a 50% radius.
39
+
40
+ | Class | Description |
41
+ |---|---|
42
+ | `is-circle` | Equal sides and a circular radius, for avatars and round media. |
43
+ | `is-square` | Equal sides with the default corner radius. |
44
+ | `is-block` | A taller full-width rectangle, for images and cards. |
45
+ | `is-rounded` | Uses the rounded corner-radius token. |
46
+ | `is-pill` | Uses a fully rounded capsule radius. |
47
+
48
+ ## CSS Variables
49
+
50
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
51
+ |---|---|---|---|---|---|---|
52
+ | `--pw_Placeholder_root_default_color` | `root` | `default` | `color` | `var(--pw_text_color)` | Colour used by the `currentcolor` fill. | Web, Capacitor, NativeScript |
53
+ | `--pw_Placeholder_root_default_background-color` | `root` | `default` | `background-color` | `currentcolor` | Placeholder fill. | Web, Capacitor, NativeScript |
54
+ | `--pw_Placeholder_root_default_opacity` | `root` | `default` | `opacity` | `0.5` | Resting fill opacity. | Web, Capacitor, NativeScript |
55
+ | `--pw_Placeholder_root_pulse_opacity` | `root` | `pulse` | `opacity` | `0.2` | Glow opacity at the midpoint of the pulse. | Web, Capacitor; NativeScript adapter |
56
+ | `--pw_Placeholder_root_default_width` | `root` | `default` | `width` | `100%` | Default width. | Web, Capacitor, NativeScript |
57
+ | `--pw_Placeholder_root_default_height` | `root` | `default` | `height` | `2.75em` | Default height, matching Button. | Web, Capacitor, NativeScript |
58
+ | `--pw_Placeholder_root_default_border-radius` | `root` | `default` | `border-radius` | `var(--pw_border-radius)` | Default corner radius, matching Button. | Web, Capacitor, NativeScript |
59
+ | `--pw_Placeholder_root_rounded_border-radius` | `root` | `rounded` | `border-radius` | `var(--pw_rounded_border-radius)` | Corner radius for `is-rounded`. | Web, Capacitor, NativeScript |
60
+ | `--pw_Placeholder_root_pill_border-radius` | `root` | `pill` | `border-radius` | `999px` | Corner radius for `is-pill`. | Web, Capacitor, NativeScript |
61
+ | `--pw_Placeholder_root_circle_width` | `root` | `circle` | `width` | `3em` | Width for `is-circle`. | Web, Capacitor, NativeScript |
62
+ | `--pw_Placeholder_root_circle_height` | `root` | `circle` | `height` | `3em` | Height for `is-circle`. | Web, Capacitor, NativeScript |
63
+ | `--pw_Placeholder_root_square_width` | `root` | `square` | `width` | `3em` | Width for `is-square`. | Web, Capacitor, NativeScript |
64
+ | `--pw_Placeholder_root_square_height` | `root` | `square` | `height` | `3em` | Height for `is-square`. | Web, Capacitor, NativeScript |
65
+ | `--pw_Placeholder_root_block_width` | `root` | `block` | `width` | `100%` | Width for `is-block`. | Web, Capacitor, NativeScript |
66
+ | `--pw_Placeholder_root_block_height` | `root` | `block` | `height` | `8em` | Height for `is-block`. | Web, Capacitor, NativeScript |
67
+ | `--pw_Placeholder_root_default_animation-duration` | `root` | `default` | `animation-duration` | `2s` | Pulse duration. | Web, Capacitor; NativeScript adapter |
68
+
69
+ ```xts
70
+ import { useMediator, withDefaultProps } from '@pastweb/x';
71
+ import { placeholderMediator } from '@pastweb/components-core/Placeholder';
72
+ import { DEFAULT_PROPS } from './constants';
73
+
74
+ export function Placeholder(props: PlaceholderProps) {
75
+ const p = withDefaultProps<PlaceholderDefaultProps>(props, DEFAULT_PROPS);
76
+ const m = useMediator<PlaceholderMediator>(placeholderMediator, p);
77
+ const s = m.state;
78
+
79
+ return (
80
+ <div
81
+ class={p.cls('Placeholder', { 'is-decorative': p.decorative }, p.className)}
82
+ role={s.role}
83
+ aria-label={s.ariaLabel}
84
+ aria-live={s.ariaLive}
85
+ aria-hidden={s.ariaHidden}
86
+ />
87
+ );
88
+ }
89
+ ```
90
+
91
+ ## Keyboard interaction
92
+
93
+ - Placeholder is not interactive. It does not enter the tab order.
94
+
95
+ ## Accessibility notes
96
+
97
+ - Placeholders are decorative by default and are removed from the accessibility
98
+ tree with `aria-hidden`.
99
+ - A non-decorative Placeholder exposes `role="status"`, a polite live region,
100
+ and `ariaLabel`. Use that mode only for a single loading signal.
101
+ - When several placeholders compose a skeleton, keep them decorative and
102
+ announce loading once on a parent region.
103
+ - The pulse animation is suppressed when the user prefers reduced motion.
104
+ - Do not use Placeholder as an unlabeled interactive control.
105
+
106
+ ## Native renderer mapping
107
+
108
+ NativeScript renderers map the shape to a native view with the configured size
109
+ and corner radius. Decorative placeholders disable native accessibility
110
+ exposure; a non-decorative placeholder maps `ariaLabel` intent to
111
+ `accessibilityLabel` and status semantics. Do not copy ARIA attributes into
112
+ native markup.
113
+
114
+ ## Known limitations
115
+
116
+ - Automated accessibility checks do not by themselves establish WCAG
117
+ conformance.
118
+ - The fill colour must remain distinguishable from the page background.
119
+
120
+ ## Standards
121
+
122
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
123
+ - WAI-ARIA status guidance: https://www.w3.org/WAI/ARIA/apg/patterns/
124
+ - GOV.UK components guidance: https://design-system.service.gov.uk/components/
@@ -0,0 +1,137 @@
1
+ # ProgressBar
2
+
3
+ ProgressBar communicates the completion of a determinate task in linear or
4
+ circular form. Its rendering contract targets WCAG 2.2 Level AA.
5
+
6
+ ## When to use
7
+
8
+ Use it when a numeric completion value is known, such as an upload or processing
9
+ task. Use a separate indeterminate status indicator when completion cannot be
10
+ calculated. Keep important status text available as accessible value text, not
11
+ only as a visual bar.
12
+
13
+ Do not use it as a multi-step form progress indicator. GOV.UK guidance is to
14
+ avoid step trackers that show every question at once.
15
+
16
+ ## Props
17
+
18
+ | PropName | Type | Required | Default | Description |
19
+ |---|---|---:|---:|---|
20
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component and part classes. |
21
+ | `ariaLabel` | `string` | no | `"Progress"` | Accessible name for the progressbar. Pass an empty string to omit the name. |
22
+ | `total` | `number` | conditional | - | Total used with `current`. |
23
+ | `current` | `number` | conditional | - | Current value used with `total`. |
24
+ | `percentage` | `number` | conditional | - | Direct percentage; clamped from 0 to 100. |
25
+ | `label` | `string \| (values) => string` | no | `"%"` | Visible and accessible value text; use an empty string to hide it. |
26
+ | `type` | `'linear' \| 'circular'` | no | `'linear'` | Visual presentation. |
27
+ | `size` | `number` | no | `100` | Circular SVG coordinate size. |
28
+ | `className` | `string` | no | `""` | Additional root classes. |
29
+
30
+ Provide either `percentage` or both `total` and `current`.
31
+
32
+ ## Utility classes
33
+
34
+ Apply these classes through `className`.
35
+
36
+ | Class | Description |
37
+ |---|---|
38
+ | `is-rounded` | Uses the rounded progress-bar radius token. |
39
+ | `is-outlined` | Uses the page background for the unfilled track so the bar reads as outlined. |
40
+ | `is-full-width` | Expands the progress bar to the available width. |
41
+
42
+ ## Mediator state
43
+
44
+ | Property | Type | Description |
45
+ |---|---|---|
46
+ | `ariaLabel` | `string \| undefined` | Normalized accessible name. Empty labels are omitted. |
47
+ | `circumference` | `number` | Circular stroke length. |
48
+ | `labelContent` | `string \| null` | Visible value label, or `null` when `label` is empty. |
49
+ | `offset` | `number` | Circular stroke offset for the remaining portion. |
50
+ | `percentage` | `number` | Completion from 0 to 100. Renderers use this for `aria-valuenow`. |
51
+ | `radius` | `number` | Circular track radius from `size`. |
52
+ | `valueText` | `string` | Accessible value text. Falls back to `percentage%` when the label is hidden. |
53
+ | `width` | `string` | Linear filler width as a percentage string. |
54
+
55
+ ## CSS Variables
56
+
57
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
58
+ |---|---|---|---|---|---|---|
59
+ | `--pw_ProgressBar_root_default_background-color` | `root` | `default` | `background-color` | `var(--pw_control_background-color)` | Unfilled track colour. | Web, Capacitor, NativeScript |
60
+ | `--pw_ProgressBar_root_default_border-color` | `root` | `default` | `border-color` | `var(--pw_border-color)` | Linear border colour. | Web, Capacitor, NativeScript |
61
+ | `--pw_ProgressBar_root_default_border-radius` | `root` | `default` | `border-radius` | `var(--pw_border-radius)` | Linear corner radius. | Web, Capacitor, NativeScript |
62
+ | `--pw_ProgressBar_root_rounded_border-radius` | `root` | `rounded` | `border-radius` | `var(--pw_rounded_border-radius)` | Corner radius for `is-rounded`. | Web, Capacitor, NativeScript |
63
+ | `--pw_ProgressBar_root_default_border-style` | `root` | `default` | `border-style` | `var(--pw_border-style)` | Linear border style. | Web, Capacitor, NativeScript |
64
+ | `--pw_ProgressBar_root_default_border-width` | `root` | `default` | `border-width` | `var(--pw_border-width)` | Linear border width. | Web, Capacitor, NativeScript |
65
+ | `--pw_ProgressBar_label_default_color` | `label` | `default` | `color` | `inherit` | Value-label colour. | Web, Capacitor, NativeScript |
66
+ | `--pw_ProgressBar_fill_default_background-color` | `fill` | `default` | `background-color` | border colour | Linear completion colour. | Web, Capacitor, NativeScript |
67
+ | `--pw_ProgressBar_fill_default_stroke` | `fill` | `default` | `stroke` | border colour | Circular completion stroke. | Web, Capacitor |
68
+ | `--pw_ProgressBar_linear_default_height` | `linear` | `default` | `height` | `2em` | Linear bar height. | Web, Capacitor, NativeScript |
69
+ | `--pw_ProgressBar_circular_default_width` | `circular` | `default` | `width` | `5em` | Circular width. | Web, Capacitor, NativeScript |
70
+ | `--pw_ProgressBar_circular_default_height` | `circular` | `default` | `height` | `5em` | Circular height. | Web, Capacitor, NativeScript |
71
+ | `--pw_ProgressBar_circle_default_stroke-width` | `circle` | `default` | `stroke-width` | `0.5em` | Circular stroke width. | Web, Capacitor |
72
+ | `--pw_ProgressBar_circle_default_stroke-linecap` | `circle` | `default` | `stroke-linecap` | `round` | Circular stroke cap. | Web, Capacitor |
73
+
74
+ ```xts
75
+ import { useMediator, withDefaultProps } from '@pastweb/x';
76
+ import { progressbarMediator } from '@pastweb/components-core/ProgressBar';
77
+ import { DEFAULT_PROPS } from './constants';
78
+
79
+ export function ProgressBar(props: ProgressBarProps) {
80
+ const p = withDefaultProps<ProgressBarDefaultProps>(props, DEFAULT_PROPS);
81
+ const m = useMediator<ProgressBarMediator>(progressbarMediator, p);
82
+ const s = m.state;
83
+
84
+ return (
85
+ <div
86
+ class={p.cls('ProgressBar', `type-${p.type}`, p.className)}
87
+ role="progressbar"
88
+ aria-label={s.ariaLabel}
89
+ aria-valuemin={0}
90
+ aria-valuemax={100}
91
+ aria-valuenow={s.percentage}
92
+ aria-valuetext={s.valueText}
93
+ >
94
+ <div #if={p.type === 'linear'} class={p.cls('linear')}>
95
+ <div class={p.cls('filler')} #style={{ width: s.width }}>
96
+ <div #if={s.labelContent} class={p.cls('label')}>{s.labelContent}</div>
97
+ </div>
98
+ </div>
99
+ <div #if={p.type === 'circular'} class={p.cls('circular')}>
100
+ <svg class={p.cls('svg')} viewBox={`0 0 ${p.size} ${p.size}`} aria-hidden="true">
101
+ <circle class={p.cls('track')} cx={p.size / 2} cy={p.size / 2} r={s.radius} />
102
+ <circle class={p.cls('fill')} cx={p.size / 2} cy={p.size / 2} r={s.radius}
103
+ stroke-dasharray={s.circumference} stroke-dashoffset={s.offset} />
104
+ </svg>
105
+ <div #if={s.labelContent} class={p.cls('label')}>{s.labelContent}</div>
106
+ </div>
107
+ </div>
108
+ );
109
+ }
110
+ ```
111
+
112
+ ## Keyboard interaction
113
+
114
+ - ProgressBar is not interactive. It does not enter the tab order.
115
+
116
+ ## Accessibility notes
117
+
118
+ - The web renderer exposes the `progressbar` role, a name, and values from 0 to
119
+ 100. The SVG is decorative.
120
+ - The default accessible name is `Progress`. An explicit empty string omits the
121
+ name.
122
+ - Visible `label` content is also used as `aria-valuetext`. Hiding the label
123
+ still exposes a numeric value text.
124
+ - Shared web styles support `forced-colors` and disable non-essential
125
+ transitions under `prefers-reduced-motion`.
126
+
127
+ ## Known limitations
128
+
129
+ - ProgressBar is determinate only. It does not represent unknown remaining time.
130
+ - Automated accessibility checks do not by themselves establish WCAG
131
+ conformance.
132
+
133
+ ## Standards
134
+
135
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
136
+ - WAI-ARIA Meter pattern: https://www.w3.org/WAI/ARIA/apg/patterns/meter/
137
+ - GOV.UK components: https://design-system.service.gov.uk/components/
@@ -0,0 +1,144 @@
1
+ # Radio
2
+
3
+ Radio presents a labelled group in which exactly one enabled option can be
4
+ selected. Its rendering contract follows the WAI-ARIA Radio Group pattern and
5
+ targets WCAG 2.2 Level AA.
6
+
7
+ ## When to use
8
+
9
+ Use radios when choices are mutually exclusive and should remain visible. Use
10
+ Select for a long option list. Provide a concise group label and clear option
11
+ labels. Do not pre-select an option unless a default is required.
12
+
13
+ ## Props
14
+
15
+ | PropName | Type | Required | Default | Description |
16
+ |---|---|---:|---:|---|
17
+ | `cls` | `(...args: ClassValue[]) => string` | yes | - | Resolves component, part, and state classes. |
18
+ | `ariaLabel` | `string` | no | `"Radio options"` | Names the group when `label` is absent. Pass an empty string to omit the name. |
19
+ | `id` | `string` | no | generated | Stable group and relationship ID base. Generated IDs use the `radio` prefix. Explicit IDs are stored as `_${id}`. |
20
+ | `label` | `string` | no | `""` | Visible group label. |
21
+ | `labelPlacement` | `Placement` | no | `'top-start'` | Group-label placement. |
22
+ | `name` | `string` | no | generated group ID | Shared native form name. |
23
+ | `options` | `RadioOption[]` | yes | - | Options to render. |
24
+ | `orientation` | `'horizontal' \| 'vertical' \| string` | no | `'horizontal'` | Option layout. |
25
+ | `disabled` | `boolean` | no | - | Disables every option and applies `is-disabled`. |
26
+ | `value` | `string` | no | `""` | Selected value. |
27
+ | `onChange` | `(value, event) => void` | no | noop | Reports selection. |
28
+ | `className` | `string` | no | `""` | Additional root classes. |
29
+
30
+ `RadioOption` accepts `label`, `value`, optional `id`, `name`, `disabled`,
31
+ `tabIndex`, and `labelPosition`. The option default is `labelPosition: 'right'`.
32
+ Do not set `tabIndex` on every option; native radios
33
+ keep only the selected or first available option in sequential tab order.
34
+
35
+ ## Mediator state
36
+
37
+ | Property | Type | Description |
38
+ |---|---|---|
39
+ | `ariaLabel` | `string \| undefined` | Normalized group name when no visible `label` is present. Empty labels are omitted. |
40
+ | `groupId` | `string` | Group ID from an explicit `id` or a generated `radio` prefix. |
41
+ | `groupLabelId` | `string \| undefined` | Label relationship ID `label${groupId}` when visible label text is present; otherwise omitted. |
42
+ | `isDisabled` | `true \| undefined` | Is `true` when the group is disabled; otherwise omitted. Renderers use this value directly for `.is-disabled`, `aria-disabled`, and native `disabled`. |
43
+ | `value` | `string` | Selected option value. |
44
+
45
+ Option IDs are `option${groupId}${index}` unless an option supplies an explicit
46
+ `id`.
47
+
48
+ ## CSS Variables
49
+
50
+ | Variable | Part | State | CSS property | Default | Description | Platforms |
51
+ |---|---|---|---|---|---|---|
52
+ | `--pw_Radio_root_default_margin` | `root` | `default` | `margin` | shared margins | Group spacing. | Web, Capacitor, NativeScript |
53
+ | `--pw_Radio_root_default_font-size` | `root` | `default` | `font-size` | `var(--pw_font-size)` | Group text size. | Web, Capacitor, NativeScript |
54
+ | `--pw_Radio_root_default_font-weight` | `root` | `default` | `font-weight` | `var(--pw_semibold_font-weight)` | Group text weight. | Web, Capacitor, NativeScript |
55
+ | `--pw_Radio_root_default_line-height` | `root` | `default` | `line-height` | `var(--pw_line-height)` | Group line height. | Web, Capacitor, NativeScript |
56
+ | `--pw_Radio_root_disabled_opacity` | `root` | `disabled` | `opacity` | `var(--pw_disabled_opacity)` | Disabled appearance. | Web, Capacitor, NativeScript |
57
+ | `--pw_Radio_option_default_margin` | `option` | `default` | `margin` | shared margins | Horizontal option spacing. | Web, Capacitor, NativeScript |
58
+ | `--pw_Radio_option_vertical_margin` | `option` | `vertical` | `margin` | `1em 0.5em` | Vertical option spacing. | Web, Capacitor, NativeScript |
59
+ | `--pw_Radio_control_default_width` | `control` | `default` | `width` | `2.375em` | Control width. | Web, Capacitor, NativeScript |
60
+ | `--pw_Radio_control_default_height` | `control` | `default` | `height` | `2.375em` | Control height. | Web, Capacitor, NativeScript |
61
+ | `--pw_Radio_control_default_background-color` | `control` | `default` | `background-color` | `var(--pw_control_background-color)` | Control background. | Web, Capacitor, NativeScript |
62
+ | `--pw_Radio_control_default_border-color` | `control` | `default` | `border-color` | `var(--pw_border-color)` | Control border colour. | Web, Capacitor, NativeScript |
63
+ | `--pw_Radio_control_default_border-radius` | `control` | `default` | `border-radius` | `var(--pw_rounded_border-radius)` | Control shape. | Web, Capacitor, NativeScript |
64
+ | `--pw_Radio_control_default_border-style` | `control` | `default` | `border-style` | `var(--pw_border-style)` | Control border style. | Web, Capacitor, NativeScript |
65
+ | `--pw_Radio_control_default_border-width` | `control` | `default` | `border-width` | `var(--pw_border-width)` | Control border width. | Web, Capacitor, NativeScript |
66
+ | `--pw_Radio_control_default_transition-duration` | `control` | `default` | `transition-duration` | `var(--pw_transition-duration)` | Indicator transition duration. | Web, Capacitor; NativeScript adapter |
67
+ | `--pw_Radio_control_hover_box-shadow` | `control` | `hover` | `box-shadow` | shared inset shadow | Hover treatment. | Web, Capacitor |
68
+ | `--pw_Radio_control_focus_box-shadow` | `control` | `focus` | `box-shadow` | shared focus shadow | Keyboard focus treatment. | Web, Capacitor |
69
+ | `--pw_Radio_indicator_default_width` | `indicator` | `default` | `width` | `1em` | Indicator width. | Web, Capacitor, NativeScript |
70
+ | `--pw_Radio_indicator_default_height` | `indicator` | `default` | `height` | `1em` | Indicator height. | Web, Capacitor, NativeScript |
71
+ | `--pw_Radio_indicator_default_background-color` | `indicator` | `default` | `background-color` | `var(--pw_text_strong_color)` | Selected indicator colour. | Web, Capacitor, NativeScript |
72
+ | `--pw_Radio_label_default_color` | `label` | `default` | `color` | `var(--pw_label_text_color)` | Label colour. | Web, Capacitor, NativeScript |
73
+
74
+ ```xts
75
+ import { useBeforeUnmount, useMediator, withDefaultProps } from '@pastweb/x';
76
+ import { radioMediator } from '@pastweb/components-core/Radio';
77
+ import { DEFAULT_PROPS } from './constants';
78
+
79
+ export function Radio(props: RadioProps) {
80
+ const p = withDefaultProps<RadioDefaultProps>(props, DEFAULT_PROPS);
81
+ const m = useMediator<RadioMediator>(radioMediator, p);
82
+ const s = m.state;
83
+
84
+ useBeforeUnmount(m.useBeforeUnmount);
85
+
86
+ return (
87
+ <div
88
+ id={s.groupId}
89
+ class={p.cls('Radio', `label-${p.labelPlacement}`, { 'is-disabled': s.isDisabled }, p.className)}
90
+ role="radiogroup"
91
+ aria-labelledby={s.groupLabelId}
92
+ aria-label={s.ariaLabel}
93
+ aria-disabled={s.isDisabled}
94
+ >
95
+ <div class={p.cls('buttons', { vertical: p.orientation === 'vertical' })}>
96
+ {p.options.map((option, index) => (
97
+ <label class={p.cls('group', `label-${option.labelPosition || 'right'}`, {
98
+ 'is-checked': s.value === `${option.value}`,
99
+ 'is-disabled': s.isDisabled || option.disabled,
100
+ })}>
101
+ <input type="radio" class={p.cls('input')} id={m.getOptionId(index, option.id)}
102
+ name={option.name || p.name || s.groupId} value={option.value}
103
+ checked={s.value === `${option.value}`} disabled={s.isDisabled || option.disabled}
104
+ @change={event => m.onChange(event.target.value, event)} />
105
+ <span class={p.cls('box')} aria-hidden="true"><span class={p.cls('indicator')} /></span>
106
+ <span class={p.cls('buttonLabel')}>{option.label}</span>
107
+ </label>
108
+ ))}
109
+ </div>
110
+ <span #if={p.label} id={s.groupLabelId} class={p.cls('label', p.labelPlacement)}>{p.label}</span>
111
+ </div>
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## Keyboard interaction
117
+
118
+ - Native radio inputs provide arrow-key selection within the group.
119
+ - `Tab` and `Shift+Tab` move sequential focus to the selected or first available
120
+ option.
121
+ - Disabled options and a disabled group are not operable.
122
+
123
+ ## Accessibility notes
124
+
125
+ - Native radio inputs provide group semantics, checked state, and form behavior.
126
+ The visual control is decorative; the native input remains in the
127
+ accessibility tree and tab order.
128
+ - The visible group label is connected through `aria-labelledby`. When it is
129
+ absent, `ariaLabel` names the group. The default name is `Radio options`.
130
+ - Disabled state is reflected through `.is-disabled`, native `disabled`, and
131
+ `aria-disabled`.
132
+ - Shared web styles provide visible keyboard focus, forced-colours treatment,
133
+ and reduced-motion behavior.
134
+
135
+ ## Known limitations
136
+
137
+ - Automated accessibility checks do not by themselves establish WCAG
138
+ conformance.
139
+
140
+ ## Standards
141
+
142
+ - WCAG 2.2: https://www.w3.org/TR/WCAG22/
143
+ - WAI-ARIA Radio Group pattern: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
144
+ - GOV.UK radios: https://design-system.service.gov.uk/components/radios/