@pienter/ui 0.3.0 → 0.5.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 +200 -0
- package/CONVENTIONS.md +297 -385
- package/README.md +3 -18
- package/components/feedback/toast/toast.css +1 -1
- package/components/form/combobox/Combobox.vue +21 -38
- package/components/form/combobox/combobox.css +1 -1
- package/components/form/form/Form.vue +1 -2
- package/components/form/label/label.css +1 -1
- package/components/form/number-field/NumberField.vue +1 -1
- package/components/form/number-field/number-field.css +1 -1
- package/components/form/radio-group/RadioGroup.vue +2 -5
- package/components/form/slider/slider.css +2 -3
- package/components/form/tags-input/tags-input.css +1 -2
- package/components/form/textarea/textarea.css +1 -1
- package/components/layout/accordion/Accordion.vue +6 -27
- package/components/layout/collapsible/Collapsible.vue +9 -19
- package/components/layout/table/table.css +3 -3
- package/components/navigation/sidebar/Sidebar.vue +326 -84
- package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
- package/components/navigation/sidebar/context.ts +21 -0
- package/components/navigation/sidebar/sidebar.css +346 -78
- package/components/navigation/sidebar/types.ts +13 -1
- package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
- package/components/overlay/command/Command.vue +10 -38
- package/components/overlay/command/command.css +1 -1
- package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
- package/components/overlay/modal/Modal.vue +7 -28
- package/components/overlay/popover/Popover.vue +9 -31
- package/components/overlay/sheet/Sheet.vue +7 -28
- package/components/overlay/tooltip/Tooltip.vue +14 -19
- package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
- package/composables/useDisclosure.ts +113 -0
- package/composables/useEventListener.ts +16 -0
- package/{utils/controllers/menu.ts → composables/useMenu.ts} +66 -102
- package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
- package/package.json +5 -39
- package/styles/0-settings/layout.css +18 -0
- package/styles/0-settings/motion.css +2 -2
- package/styles/0-settings/spacing.css +3 -1
- package/utils/navigation/sidebar.ts +97 -0
- package/utils/validation/form.ts +6 -9
- package/components/action/button/Button.astro +0 -95
- package/components/action/button/IconButton.astro +0 -86
- package/components/display/avatar/Avatar.astro +0 -17
- package/components/display/avatar/AvatarStack.astro +0 -9
- package/components/display/badge/Badge.astro +0 -15
- package/components/display/empty/Empty.astro +0 -9
- package/components/display/icon/Icon.astro +0 -52
- package/components/feedback/alert/Alert.astro +0 -52
- package/components/feedback/progress/Progress.astro +0 -68
- package/components/feedback/skeleton/Skeleton.astro +0 -32
- package/components/feedback/spinner/Spinner.astro +0 -25
- package/components/feedback/toast/Toast.astro +0 -50
- package/components/form/checkbox/Checkbox.astro +0 -79
- package/components/form/date-input/DateInput.astro +0 -105
- package/components/form/form/Form.astro +0 -106
- package/components/form/input-otp/InputOTP.astro +0 -147
- package/components/form/label/Label.astro +0 -13
- package/components/form/number-field/NumberField.astro +0 -142
- package/components/form/radio-group/RadioGroup.astro +0 -105
- package/components/form/select/Select.astro +0 -105
- package/components/form/slider/Slider.astro +0 -205
- package/components/form/switch/Switch.astro +0 -75
- package/components/form/tags-input/TagsInput.astro +0 -153
- package/components/form/text-input/TextInput.astro +0 -84
- package/components/form/textarea/Textarea.astro +0 -86
- package/components/layout/card/Card.astro +0 -13
- package/components/layout/separator/Separator.astro +0 -31
- package/components/layout/table/Table.astro +0 -116
- package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
- package/components/navigation/navbar/Navbar.astro +0 -62
- package/components/navigation/pagination/PaginationFooter.astro +0 -24
- package/components/navigation/sidebar/Sidebar.astro +0 -132
- package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
- package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
- package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
- package/components/overlay/modal/Modal.astro +0 -66
- package/utils/controllers/disclosure.ts +0 -117
- package/utils/controllers/form.ts +0 -524
- package/utils/controllers/index.ts +0 -39
- package/utils/controllers/number-field.ts +0 -103
- package/utils/controllers/otp.ts +0 -252
- package/utils/controllers/sidebar.ts +0 -610
- package/utils/controllers/slider.ts +0 -336
- package/utils/controllers/tags-input.ts +0 -255
- package/utils/controllers/toast.ts +0 -426
package/utils/validation/form.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { type Validator, validate } from './rules.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Placeholder shape for form-submit responses, consumed
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* shape is **subject to change when ts-sdk publishes the canonical
|
|
9
|
-
* response shape**, at which point both implementations are updated
|
|
10
|
-
* together.
|
|
4
|
+
* Placeholder shape for form-submit responses, consumed by `Form.vue`'s
|
|
5
|
+
* orchestration. Locked in design D13 / spec "Unified Form Submit Response
|
|
6
|
+
* Handling"; the exact shape is **subject to change when ts-sdk publishes
|
|
7
|
+
* the canonical response shape**.
|
|
11
8
|
*/
|
|
12
9
|
export interface FormSubmitResponse {
|
|
13
10
|
/** Whether the submit succeeded. */
|
|
@@ -28,8 +25,8 @@ export interface FormSubmitResponse {
|
|
|
28
25
|
|
|
29
26
|
/**
|
|
30
27
|
* Run a per-field validator map against a `FormData` instance and return
|
|
31
|
-
* a `Record<string, string[]>` of field errors. Pure function;
|
|
32
|
-
*
|
|
28
|
+
* a `Record<string, string[]>` of field errors. Pure function; used by
|
|
29
|
+
* Form.vue's orchestration.
|
|
33
30
|
*
|
|
34
31
|
* Field values are read via `formData.get(name)` and coerced to strings.
|
|
35
32
|
* Fields without an entry in `validators` are not checked. Fields whose
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './button.css';
|
|
3
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
4
|
-
import Spinner from '../../feedback/spinner/Spinner.astro';
|
|
5
|
-
import type { IconName } from '../../../icons/index.js';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
|
|
9
|
-
size?: 'sm' | 'md';
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
loading?: boolean;
|
|
12
|
-
type?: 'button' | 'submit' | 'reset';
|
|
13
|
-
block?: boolean;
|
|
14
|
-
href?: string;
|
|
15
|
-
/** Leading icon from the shipped set. Use the `icon` slot for a custom <svg>. */
|
|
16
|
-
icon?: IconName;
|
|
17
|
-
/** Anchor target, only applied when `href` is set (renders an <a>). */
|
|
18
|
-
target?: string;
|
|
19
|
-
/** Anchor rel; defaults to a safe value for `target="_blank"`. */
|
|
20
|
-
rel?: string;
|
|
21
|
-
id?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
variant = 'secondary',
|
|
26
|
-
size = 'md',
|
|
27
|
-
disabled = false,
|
|
28
|
-
loading = false,
|
|
29
|
-
type = 'button',
|
|
30
|
-
block = false,
|
|
31
|
-
href,
|
|
32
|
-
icon,
|
|
33
|
-
target,
|
|
34
|
-
rel,
|
|
35
|
-
id,
|
|
36
|
-
...rest
|
|
37
|
-
} = Astro.props;
|
|
38
|
-
|
|
39
|
-
const Tag = href ? 'a' : 'button';
|
|
40
|
-
const isInert = disabled || loading;
|
|
41
|
-
// Button tier -> Icon size for the leading glyph (the Spinner is sized in button.css)
|
|
42
|
-
const iconSize = size === 'sm' ? 'sm' : 'md';
|
|
43
|
-
const hasIconSlot = Astro.slots.has('icon');
|
|
44
|
-
const hasIcon = !!icon || hasIconSlot;
|
|
45
|
-
const resolvedRel = href
|
|
46
|
-
? (rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined))
|
|
47
|
-
: undefined;
|
|
48
|
-
|
|
49
|
-
// the icon slot wins over the `icon` name; flag the redundant combination in dev
|
|
50
|
-
if (import.meta.env.DEV && icon && hasIconSlot) {
|
|
51
|
-
console.warn(
|
|
52
|
-
'[pienter-ui] Button received both an `icon` name and an `icon` slot; the slot is used and `icon` is ignored.',
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
<Tag
|
|
58
|
-
id={id}
|
|
59
|
-
class="pui-btn"
|
|
60
|
-
type={href ? undefined : type}
|
|
61
|
-
href={href}
|
|
62
|
-
target={href ? target : undefined}
|
|
63
|
-
rel={resolvedRel}
|
|
64
|
-
disabled={href ? undefined : isInert}
|
|
65
|
-
aria-disabled={href && isInert ? 'true' : undefined}
|
|
66
|
-
tabindex={href && isInert ? '-1' : undefined}
|
|
67
|
-
aria-busy={loading ? 'true' : undefined}
|
|
68
|
-
data-variant={variant}
|
|
69
|
-
data-size={size}
|
|
70
|
-
data-block={block ? 'true' : undefined}
|
|
71
|
-
data-state={loading ? 'loading' : undefined}
|
|
72
|
-
{...rest}
|
|
73
|
-
>
|
|
74
|
-
{
|
|
75
|
-
loading && (
|
|
76
|
-
<span class="pui-btn__spinner" aria-hidden="true">
|
|
77
|
-
<Spinner />
|
|
78
|
-
</span>
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
{
|
|
82
|
-
hasIcon && (
|
|
83
|
-
<span class="pui-btn__icon" aria-hidden="true">
|
|
84
|
-
{hasIconSlot ? (
|
|
85
|
-
<Icon size={iconSize}>
|
|
86
|
-
<slot name="icon" />
|
|
87
|
-
</Icon>
|
|
88
|
-
) : (
|
|
89
|
-
<Icon name={icon} size={iconSize} />
|
|
90
|
-
)}
|
|
91
|
-
</span>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
<span class="pui-btn__label"><slot /></span>
|
|
95
|
-
</Tag>
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './button.css';
|
|
3
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
4
|
-
import Spinner from '../../feedback/spinner/Spinner.astro';
|
|
5
|
-
import type { IconName } from '../../../icons/index.js';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
/** Accessible name for the icon-only control (required — there is no visible label). */
|
|
9
|
-
label: string;
|
|
10
|
-
/** Shipped icon to render. Omit and pass your own SVG via the default slot instead. */
|
|
11
|
-
name?: IconName;
|
|
12
|
-
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
13
|
-
size?: 'sm' | 'md';
|
|
14
|
-
disabled?: boolean;
|
|
15
|
-
loading?: boolean;
|
|
16
|
-
type?: 'button' | 'submit' | 'reset';
|
|
17
|
-
href?: string;
|
|
18
|
-
/** Anchor target, only applied when `href` is set (renders an <a>). */
|
|
19
|
-
target?: string;
|
|
20
|
-
/** Anchor rel; defaults to a safe value for `target="_blank"`. */
|
|
21
|
-
rel?: string;
|
|
22
|
-
id?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
label,
|
|
27
|
-
name,
|
|
28
|
-
variant = 'secondary',
|
|
29
|
-
size = 'md',
|
|
30
|
-
disabled = false,
|
|
31
|
-
loading = false,
|
|
32
|
-
type = 'button',
|
|
33
|
-
href,
|
|
34
|
-
target,
|
|
35
|
-
rel,
|
|
36
|
-
id,
|
|
37
|
-
...rest
|
|
38
|
-
} = Astro.props;
|
|
39
|
-
|
|
40
|
-
const Tag = href ? 'a' : 'button';
|
|
41
|
-
const isInert = disabled || loading;
|
|
42
|
-
const iconSize = size === 'sm' ? 'sm' : 'md';
|
|
43
|
-
const hasSlot = Astro.slots.has('default');
|
|
44
|
-
const resolvedRel = href
|
|
45
|
-
? (rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined))
|
|
46
|
-
: undefined;
|
|
47
|
-
|
|
48
|
-
// the slot wins over the `name`; flag the redundant combination in dev
|
|
49
|
-
if (import.meta.env.DEV && name && hasSlot) {
|
|
50
|
-
console.warn(
|
|
51
|
-
'[pienter-ui] IconButton received both a `name` and slot content; the slot is used and `name` is ignored.',
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
<Tag
|
|
57
|
-
id={id}
|
|
58
|
-
class="pui-icon-btn"
|
|
59
|
-
type={href ? undefined : type}
|
|
60
|
-
href={href}
|
|
61
|
-
target={href ? target : undefined}
|
|
62
|
-
rel={resolvedRel}
|
|
63
|
-
disabled={href ? undefined : isInert}
|
|
64
|
-
aria-disabled={href && isInert ? 'true' : undefined}
|
|
65
|
-
tabindex={href && isInert ? '-1' : undefined}
|
|
66
|
-
aria-busy={loading ? 'true' : undefined}
|
|
67
|
-
aria-label={label}
|
|
68
|
-
data-variant={variant}
|
|
69
|
-
data-size={size}
|
|
70
|
-
data-state={loading ? 'loading' : undefined}
|
|
71
|
-
{...rest}
|
|
72
|
-
>
|
|
73
|
-
{
|
|
74
|
-
loading ? (
|
|
75
|
-
<span class="pui-btn__spinner" aria-hidden="true">
|
|
76
|
-
<Spinner />
|
|
77
|
-
</span>
|
|
78
|
-
) : hasSlot ? (
|
|
79
|
-
<Icon size={iconSize}>
|
|
80
|
-
<slot />
|
|
81
|
-
</Icon>
|
|
82
|
-
) : (
|
|
83
|
-
<Icon name={name} size={iconSize} />
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
</Tag>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './avatar.css';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
size?: 'sm' | 'lg';
|
|
6
|
-
tone?: 'brand' | 'solid';
|
|
7
|
-
src?: string;
|
|
8
|
-
alt?: string;
|
|
9
|
-
initials?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const { size, tone, src, alt = '', initials, ...rest } = Astro.props;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
<span class="pui-avatar" data-size={size} data-tone={tone} {...rest}>
|
|
16
|
-
{src ? <img src={src} alt={alt} /> : initials ? initials : <slot />}
|
|
17
|
-
</span>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './badge.css';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
tone?: 'brand' | 'success' | 'warning' | 'danger' | 'solid';
|
|
6
|
-
dot?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const { tone, dot = false, ...rest } = Astro.props;
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<span class="pui-badge" data-tone={tone} {...rest}>
|
|
13
|
-
{dot && <span class="pui-badge__dot" />}
|
|
14
|
-
<slot />
|
|
15
|
-
</span>
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './icon.css';
|
|
3
|
-
import { icons, type IconName } from '../../../icons/index.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
/** Shipped icon to render. Omit and pass a custom <svg> via the default slot. */
|
|
7
|
-
name?: IconName;
|
|
8
|
-
size?: 'sm' | 'md' | 'lg';
|
|
9
|
-
/** Continuous animation applied to the glyph (e.g. a loading spinner). */
|
|
10
|
-
animation?: 'spin';
|
|
11
|
-
label?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const { name, size = 'md', animation, label, ...rest } = Astro.props;
|
|
15
|
-
const hasSlot = Astro.slots.has('default');
|
|
16
|
-
|
|
17
|
-
// slot wins over name; flag the redundant combination in development
|
|
18
|
-
if (import.meta.env.DEV && name && hasSlot) {
|
|
19
|
-
console.warn(
|
|
20
|
-
'[pienter-ui] Icon received both a `name` and slot content; the slot is used and `name` is ignored.',
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const svg = name ? icons[name] : '';
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
hasSlot ? (
|
|
29
|
-
<span
|
|
30
|
-
class="pui-icon"
|
|
31
|
-
data-size={size}
|
|
32
|
-
data-animation={animation}
|
|
33
|
-
aria-hidden={!label}
|
|
34
|
-
aria-label={label}
|
|
35
|
-
role={label ? 'img' : undefined}
|
|
36
|
-
{...rest}
|
|
37
|
-
>
|
|
38
|
-
<slot />
|
|
39
|
-
</span>
|
|
40
|
-
) : (
|
|
41
|
-
<span
|
|
42
|
-
class="pui-icon"
|
|
43
|
-
data-size={size}
|
|
44
|
-
data-animation={animation}
|
|
45
|
-
aria-hidden={!label}
|
|
46
|
-
aria-label={label}
|
|
47
|
-
role={label ? 'img' : undefined}
|
|
48
|
-
{...rest}
|
|
49
|
-
set:html={svg}
|
|
50
|
-
/>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './alert.css';
|
|
3
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
4
|
-
import type { IconName } from '../../../icons/index.js';
|
|
5
|
-
|
|
6
|
-
type Tone = 'brand' | 'success' | 'warning' | 'danger';
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
tone?: Tone;
|
|
10
|
-
title?: string;
|
|
11
|
-
/** Override the tone's default icon with a shipped one. Use the `icon` slot for a custom <svg>. */
|
|
12
|
-
icon?: IconName;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const toneIconMap: Record<Tone, IconName> = {
|
|
16
|
-
brand: 'info',
|
|
17
|
-
success: 'circle-check',
|
|
18
|
-
warning: 'alert-triangle',
|
|
19
|
-
danger: 'circle-alert',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const { tone, title, icon, ...rest } = Astro.props;
|
|
23
|
-
const resolvedIcon: IconName = icon ?? (tone ? toneIconMap[tone] : 'info');
|
|
24
|
-
const hasIconSlot = Astro.slots.has('icon');
|
|
25
|
-
|
|
26
|
-
// the icon slot wins over the `icon` name; flag the redundant combination in dev
|
|
27
|
-
if (import.meta.env.DEV && icon && hasIconSlot) {
|
|
28
|
-
console.warn(
|
|
29
|
-
'[pienter-ui] Alert received both an `icon` name and an `icon` slot; the slot is used and `icon` is ignored.',
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
<div class="pui-alert" data-tone={tone} role="alert" {...rest}>
|
|
35
|
-
<span class="pui-alert__icon">
|
|
36
|
-
{
|
|
37
|
-
hasIconSlot ? (
|
|
38
|
-
<Icon size="md">
|
|
39
|
-
<slot name="icon" />
|
|
40
|
-
</Icon>
|
|
41
|
-
) : (
|
|
42
|
-
<Icon name={resolvedIcon} size="md" />
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
</span>
|
|
46
|
-
<div class="pui-alert__content">
|
|
47
|
-
{title && <p class="pui-alert__title">{title}</p>}
|
|
48
|
-
<div class="pui-alert__body">
|
|
49
|
-
<slot />
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './progress.css';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
/**
|
|
6
|
-
* Current value of the progress bar. When `null` or `undefined`, the
|
|
7
|
-
* component renders the indeterminate ("loading") state per WAI-ARIA
|
|
8
|
-
* convention — `aria-valuenow` is OMITTED from the wrapper so assistive
|
|
9
|
-
* technology announces an indeterminate progressbar.
|
|
10
|
-
*/
|
|
11
|
-
value?: number | null;
|
|
12
|
-
/** Maximum value of the range. */
|
|
13
|
-
max?: number;
|
|
14
|
-
/** Minimum value of the range. */
|
|
15
|
-
min?: number;
|
|
16
|
-
/**
|
|
17
|
-
* Accessible name for the progressbar — emitted as `aria-label`. Omit
|
|
18
|
-
* when an external `aria-labelledby` is supplied via `...rest`.
|
|
19
|
-
*/
|
|
20
|
-
label?: string;
|
|
21
|
-
/** Tone variant — drives indicator color via `data-tone`. */
|
|
22
|
-
tone?: 'brand' | 'success' | 'warning' | 'danger';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
value = null,
|
|
27
|
-
max = 100,
|
|
28
|
-
min = 0,
|
|
29
|
-
label,
|
|
30
|
-
tone = 'brand',
|
|
31
|
-
...rest
|
|
32
|
-
} = Astro.props;
|
|
33
|
-
|
|
34
|
-
const isIndeterminate = value === null || value === undefined;
|
|
35
|
-
|
|
36
|
-
const clampedValue = isIndeterminate
|
|
37
|
-
? min
|
|
38
|
-
: Math.min(Math.max(value as number, min), max);
|
|
39
|
-
|
|
40
|
-
const span = max - min;
|
|
41
|
-
const percent =
|
|
42
|
-
isIndeterminate || span <= 0 ? 0 : ((clampedValue - min) / span) * 100;
|
|
43
|
-
|
|
44
|
-
const state: 'loading' | 'in-progress' | 'complete' = isIndeterminate
|
|
45
|
-
? 'loading'
|
|
46
|
-
: clampedValue === max
|
|
47
|
-
? 'complete'
|
|
48
|
-
: 'in-progress';
|
|
49
|
-
|
|
50
|
-
const indicatorStyle: Record<string, string> = {};
|
|
51
|
-
if (!isIndeterminate) {
|
|
52
|
-
indicatorStyle.width = `${percent}%`;
|
|
53
|
-
}
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
<div
|
|
57
|
-
class="pui-progress"
|
|
58
|
-
role="progressbar"
|
|
59
|
-
aria-valuenow={isIndeterminate ? undefined : clampedValue}
|
|
60
|
-
aria-valuemin={min}
|
|
61
|
-
aria-valuemax={max}
|
|
62
|
-
aria-label={label}
|
|
63
|
-
data-state={state}
|
|
64
|
-
data-tone={tone}
|
|
65
|
-
{...rest}
|
|
66
|
-
>
|
|
67
|
-
<div class="pui-progress__indicator" style={indicatorStyle}></div>
|
|
68
|
-
</div>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './skeleton.css';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
variant?: 'text' | 'rect' | 'circle';
|
|
6
|
-
width?: string;
|
|
7
|
-
height?: string;
|
|
8
|
-
rounded?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
variant = 'rect',
|
|
13
|
-
width,
|
|
14
|
-
height,
|
|
15
|
-
rounded = false,
|
|
16
|
-
...rest
|
|
17
|
-
} = Astro.props;
|
|
18
|
-
|
|
19
|
-
const inlineStyle: Record<string, string> = {};
|
|
20
|
-
if (width) inlineStyle.width = width;
|
|
21
|
-
if (height) inlineStyle.height = height;
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
<div
|
|
25
|
-
class="pui-skeleton"
|
|
26
|
-
data-variant={variant}
|
|
27
|
-
data-rounded={rounded ? '' : undefined}
|
|
28
|
-
style={inlineStyle}
|
|
29
|
-
aria-hidden="true"
|
|
30
|
-
{...rest}
|
|
31
|
-
>
|
|
32
|
-
</div>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
// Spinner (Astro) — same contract as Spinner.vue; composes Icon so the spin stays in icon.css.
|
|
3
|
-
import './spinner.css';
|
|
4
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
8
|
-
/** Accessible name; supply it when the spinner is the only signal. See Spinner.vue. */
|
|
9
|
-
label?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const { size = 'md', label, ...rest } = Astro.props;
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
<span
|
|
16
|
-
class="pui-spinner"
|
|
17
|
-
data-size={size}
|
|
18
|
-
role={label ? 'status' : undefined}
|
|
19
|
-
aria-hidden={label ? undefined : 'true'}
|
|
20
|
-
{...rest}
|
|
21
|
-
>
|
|
22
|
-
<Icon name="loader" animation="spin" />
|
|
23
|
-
{/* Real text, not an aria-label — see Spinner.vue. */}
|
|
24
|
-
{label && <span class="pui-spinner__label">{label}</span>}
|
|
25
|
-
</span>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './toast.css';
|
|
3
|
-
import Icon from '../../display/icon/Icon.astro';
|
|
4
|
-
import type { IconName } from '../../../icons/index.js';
|
|
5
|
-
|
|
6
|
-
type Tone = 'brand' | 'success' | 'warning' | 'danger';
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
tone?: Tone;
|
|
10
|
-
title: string;
|
|
11
|
-
message?: string;
|
|
12
|
-
dismissible?: boolean;
|
|
13
|
-
icon?: IconName;
|
|
14
|
-
action?: { label: string };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const toneIconMap: Record<Tone, IconName> = {
|
|
18
|
-
brand: 'info',
|
|
19
|
-
success: 'circle-check',
|
|
20
|
-
warning: 'alert-triangle',
|
|
21
|
-
danger: 'circle-alert',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const { tone, title, message, dismissible = true, icon, action } = Astro.props;
|
|
25
|
-
const resolvedIcon: IconName = icon ?? (tone ? toneIconMap[tone] : 'info');
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
<div class="pui-toast" data-tone={tone}>
|
|
29
|
-
<span class="pui-toast__icon" aria-hidden="true">
|
|
30
|
-
<Icon name={resolvedIcon} size="sm" />
|
|
31
|
-
</span>
|
|
32
|
-
<div class="pui-toast__body">
|
|
33
|
-
<p class="pui-toast__title">{title}</p>
|
|
34
|
-
{message && <p class="pui-toast__msg">{message}</p>}
|
|
35
|
-
</div>
|
|
36
|
-
{
|
|
37
|
-
action && (
|
|
38
|
-
<button class="pui-toast__action" type="button">
|
|
39
|
-
{action.label}
|
|
40
|
-
</button>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
{
|
|
44
|
-
dismissible && (
|
|
45
|
-
<button class="pui-toast__close" type="button" aria-label="Close">
|
|
46
|
-
<Icon name="x" size="sm" />
|
|
47
|
-
</button>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
</div>
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import './checkbox.css';
|
|
3
|
-
import { generateId } from '../../../utils/a11y/id.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
id?: string;
|
|
7
|
-
label: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
checked?: boolean;
|
|
10
|
-
required?: boolean;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
hint?: string;
|
|
13
|
-
errors?: string[];
|
|
14
|
-
status?: 'error' | 'success';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
id,
|
|
19
|
-
label,
|
|
20
|
-
name,
|
|
21
|
-
checked = false,
|
|
22
|
-
required = false,
|
|
23
|
-
disabled = false,
|
|
24
|
-
hint,
|
|
25
|
-
errors = [],
|
|
26
|
-
status,
|
|
27
|
-
...rest
|
|
28
|
-
} = Astro.props;
|
|
29
|
-
|
|
30
|
-
const inputId = id ?? generateId('checkbox');
|
|
31
|
-
const hintId = `${inputId}-hint`;
|
|
32
|
-
const errorsId = `${inputId}-errors`;
|
|
33
|
-
|
|
34
|
-
const hasErrors = errors.length > 0;
|
|
35
|
-
const computedStatus = status ?? (hasErrors ? 'error' : undefined);
|
|
36
|
-
|
|
37
|
-
const describedByIds: string[] = [];
|
|
38
|
-
if (hint) describedByIds.push(hintId);
|
|
39
|
-
if (hasErrors) describedByIds.push(errorsId);
|
|
40
|
-
const describedBy =
|
|
41
|
-
describedByIds.length > 0 ? describedByIds.join(' ') : undefined;
|
|
42
|
-
|
|
43
|
-
const ariaChecked = checked ? 'true' : 'false';
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
<div class="pui-field" data-layout="inline" data-status={computedStatus}>
|
|
47
|
-
<input
|
|
48
|
-
class="pui-checkbox"
|
|
49
|
-
type="checkbox"
|
|
50
|
-
id={inputId}
|
|
51
|
-
name={name}
|
|
52
|
-
checked={checked}
|
|
53
|
-
required={required}
|
|
54
|
-
disabled={disabled}
|
|
55
|
-
aria-checked={ariaChecked}
|
|
56
|
-
aria-invalid={hasErrors ? 'true' : undefined}
|
|
57
|
-
aria-describedby={describedBy}
|
|
58
|
-
{...rest}
|
|
59
|
-
/>
|
|
60
|
-
<label class="pui-field__label" for={inputId}>
|
|
61
|
-
{label}
|
|
62
|
-
</label>
|
|
63
|
-
{
|
|
64
|
-
hint && (
|
|
65
|
-
<p id={hintId} class="pui-field__hint">
|
|
66
|
-
{hint}
|
|
67
|
-
</p>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
{
|
|
71
|
-
hasErrors && (
|
|
72
|
-
<ul id={errorsId} class="pui-field__hint" role="alert">
|
|
73
|
-
{errors.map((error) => (
|
|
74
|
-
<li>{error}</li>
|
|
75
|
-
))}
|
|
76
|
-
</ul>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
</div>
|