@happyvertical/smrt-ui 0.30.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/AGENTS.md +50 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/dist/actions/__tests__/ripple.test.js +28 -0
- package/dist/actions/permission.d.ts +34 -0
- package/dist/actions/permission.d.ts.map +1 -0
- package/dist/actions/permission.js +70 -0
- package/dist/actions/ripple.d.ts +7 -0
- package/dist/actions/ripple.d.ts.map +1 -0
- package/dist/actions/ripple.js +65 -0
- package/dist/components/calendar/Calendar.svelte +520 -0
- package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
- package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -0
- package/dist/components/calendar/DayView.svelte +389 -0
- package/dist/components/calendar/DayView.svelte.d.ts +13 -0
- package/dist/components/calendar/DayView.svelte.d.ts.map +1 -0
- package/dist/components/calendar/index.d.ts +6 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +5 -0
- package/dist/components/chat/MessageBubble.svelte +126 -0
- package/dist/components/chat/MessageBubble.svelte.d.ts +30 -0
- package/dist/components/chat/MessageBubble.svelte.d.ts.map +1 -0
- package/dist/components/chat/ReactionPicker.svelte +89 -0
- package/dist/components/chat/ReactionPicker.svelte.d.ts +19 -0
- package/dist/components/chat/ReactionPicker.svelte.d.ts.map +1 -0
- package/dist/components/chat/TypingIndicator.svelte +90 -0
- package/dist/components/chat/TypingIndicator.svelte.d.ts +17 -0
- package/dist/components/chat/TypingIndicator.svelte.d.ts.map +1 -0
- package/dist/components/chat/__tests__/chat-primitives.test.js +67 -0
- package/dist/components/chat/index.d.ts +10 -0
- package/dist/components/chat/index.d.ts.map +1 -0
- package/dist/components/chat/index.js +9 -0
- package/dist/components/data/DataTable.svelte +519 -0
- package/dist/components/data/DataTable.svelte.d.ts +49 -0
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -0
- package/dist/components/data/__tests__/DataTable.test.js +48 -0
- package/dist/components/data/__tests__/data-table-helpers.test.js +36 -0
- package/dist/components/data/index.d.ts +6 -0
- package/dist/components/data/index.d.ts.map +1 -0
- package/dist/components/data/index.js +5 -0
- package/dist/components/data/types.d.ts +104 -0
- package/dist/components/data/types.d.ts.map +1 -0
- package/dist/components/data/types.js +45 -0
- package/dist/components/display/ConfidenceBadge.svelte +142 -0
- package/dist/components/display/ConfidenceBadge.svelte.d.ts +25 -0
- package/dist/components/display/ConfidenceBadge.svelte.d.ts.map +1 -0
- package/dist/components/display/CurrencyDisplay.svelte +106 -0
- package/dist/components/display/CurrencyDisplay.svelte.d.ts +30 -0
- package/dist/components/display/CurrencyDisplay.svelte.d.ts.map +1 -0
- package/dist/components/display/DateDisplay.svelte +122 -0
- package/dist/components/display/DateDisplay.svelte.d.ts +24 -0
- package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -0
- package/dist/components/display/Icon.svelte +77 -0
- package/dist/components/display/Icon.svelte.d.ts +28 -0
- package/dist/components/display/Icon.svelte.d.ts.map +1 -0
- package/dist/components/display/StatusBadge.svelte +256 -0
- package/dist/components/display/StatusBadge.svelte.d.ts +24 -0
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -0
- package/dist/components/display/__tests__/ConfidenceBadge.test.js +96 -0
- package/dist/components/display/__tests__/CurrencyDisplay.test.js +114 -0
- package/dist/components/display/__tests__/DateDisplay.test.js +114 -0
- package/dist/components/display/__tests__/Icon.test.js +93 -0
- package/dist/components/display/__tests__/StatusBadge.test.js +98 -0
- package/dist/components/display/index.d.ts +10 -0
- package/dist/components/display/index.d.ts.map +1 -0
- package/dist/components/display/index.js +9 -0
- package/dist/components/display/types.d.ts +5 -0
- package/dist/components/display/types.d.ts.map +1 -0
- package/dist/components/display/types.js +4 -0
- package/dist/components/feedback/ConfirmDialog.svelte +226 -0
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts +25 -0
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -0
- package/dist/components/feedback/LoadingOverlay.svelte +281 -0
- package/dist/components/feedback/LoadingOverlay.svelte.d.ts +31 -0
- package/dist/components/feedback/LoadingOverlay.svelte.d.ts.map +1 -0
- package/dist/components/feedback/Modal.svelte +393 -0
- package/dist/components/feedback/Modal.svelte.d.ts +46 -0
- package/dist/components/feedback/Modal.svelte.d.ts.map +1 -0
- package/dist/components/feedback/ProgressBar.svelte +162 -0
- package/dist/components/feedback/ProgressBar.svelte.d.ts +21 -0
- package/dist/components/feedback/ProgressBar.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +111 -0
- package/dist/components/feedback/__tests__/LoadingOverlay.test.js +99 -0
- package/dist/components/feedback/__tests__/Modal.test.js +72 -0
- package/dist/components/feedback/__tests__/ProgressBar.test.js +89 -0
- package/dist/components/feedback/index.d.ts +8 -0
- package/dist/components/feedback/index.d.ts.map +1 -0
- package/dist/components/feedback/index.js +10 -0
- package/dist/components/layout/Container.svelte +53 -0
- package/dist/components/layout/Container.svelte.d.ts +11 -0
- package/dist/components/layout/Container.svelte.d.ts.map +1 -0
- package/dist/components/layout/EmptyState.svelte +187 -0
- package/dist/components/layout/EmptyState.svelte.d.ts +28 -0
- package/dist/components/layout/EmptyState.svelte.d.ts.map +1 -0
- package/dist/components/layout/Footer.svelte +63 -0
- package/dist/components/layout/Footer.svelte.d.ts +8 -0
- package/dist/components/layout/Footer.svelte.d.ts.map +1 -0
- package/dist/components/layout/Grid.svelte +241 -0
- package/dist/components/layout/Grid.svelte.d.ts +56 -0
- package/dist/components/layout/Grid.svelte.d.ts.map +1 -0
- package/dist/components/layout/Header.svelte +86 -0
- package/dist/components/layout/Header.svelte.d.ts +9 -0
- package/dist/components/layout/Header.svelte.d.ts.map +1 -0
- package/dist/components/layout/Masthead.svelte +219 -0
- package/dist/components/layout/Masthead.svelte.d.ts +13 -0
- package/dist/components/layout/Masthead.svelte.d.ts.map +1 -0
- package/dist/components/layout/PageHeader.svelte +131 -0
- package/dist/components/layout/PageHeader.svelte.d.ts +26 -0
- package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -0
- package/dist/components/layout/SummaryCard.svelte +203 -0
- package/dist/components/layout/SummaryCard.svelte.d.ts +20 -0
- package/dist/components/layout/SummaryCard.svelte.d.ts.map +1 -0
- package/dist/components/layout/__tests__/Container.test.js +62 -0
- package/dist/components/layout/__tests__/EmptyState.test.js +83 -0
- package/dist/components/layout/__tests__/Footer.test.js +50 -0
- package/dist/components/layout/__tests__/Grid.test.js +121 -0
- package/dist/components/layout/__tests__/Header.test.js +48 -0
- package/dist/components/layout/__tests__/Masthead.test.js +93 -0
- package/dist/components/layout/__tests__/PageHeader.test.js +80 -0
- package/dist/components/layout/__tests__/SummaryCard.test.js +82 -0
- package/dist/components/layout/index.d.ts +12 -0
- package/dist/components/layout/index.d.ts.map +1 -0
- package/dist/components/layout/index.js +11 -0
- package/dist/components/memberships/MembershipCard.svelte +163 -0
- package/dist/components/memberships/MembershipCard.svelte.d.ts +12 -0
- package/dist/components/memberships/MembershipCard.svelte.d.ts.map +1 -0
- package/dist/components/memberships/MembershipList.svelte +98 -0
- package/dist/components/memberships/MembershipList.svelte.d.ts +19 -0
- package/dist/components/memberships/MembershipList.svelte.d.ts.map +1 -0
- package/dist/components/nav/FilterChips.svelte +152 -0
- package/dist/components/nav/FilterChips.svelte.d.ts +19 -0
- package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -0
- package/dist/components/nav/Tabs.svelte +252 -0
- package/dist/components/nav/Tabs.svelte.d.ts +34 -0
- package/dist/components/nav/Tabs.svelte.d.ts.map +1 -0
- package/dist/components/nav/__tests__/FilterChips.test.js +94 -0
- package/dist/components/nav/__tests__/Tabs.test.js +128 -0
- package/dist/components/nav/index.d.ts +7 -0
- package/dist/components/nav/index.d.ts.map +1 -0
- package/dist/components/nav/index.js +6 -0
- package/dist/components/nav/types.d.ts +24 -0
- package/dist/components/nav/types.d.ts.map +1 -0
- package/dist/components/nav/types.js +4 -0
- package/dist/components/permissions/PermissionCheck.svelte +45 -0
- package/dist/components/permissions/PermissionCheck.svelte.d.ts +19 -0
- package/dist/components/permissions/PermissionCheck.svelte.d.ts.map +1 -0
- package/dist/components/roles/RoleBadge.svelte +84 -0
- package/dist/components/roles/RoleBadge.svelte.d.ts +13 -0
- package/dist/components/roles/RoleBadge.svelte.d.ts.map +1 -0
- package/dist/components/roles/RoleSelector.svelte +216 -0
- package/dist/components/roles/RoleSelector.svelte.d.ts +13 -0
- package/dist/components/roles/RoleSelector.svelte.d.ts.map +1 -0
- package/dist/components/theme/ThemeProvider.svelte +71 -0
- package/dist/components/theme/ThemeProvider.svelte.d.ts +10 -0
- package/dist/components/theme/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/components/theme/context.svelte.d.ts +15 -0
- package/dist/components/theme/context.svelte.d.ts.map +1 -0
- package/dist/components/theme/context.svelte.js +42 -0
- package/dist/components/theme/index.d.ts +3 -0
- package/dist/components/theme/index.d.ts.map +1 -0
- package/dist/components/theme/index.js +2 -0
- package/dist/components/ui/Avatar.svelte +167 -0
- package/dist/components/ui/Avatar.svelte.d.ts +26 -0
- package/dist/components/ui/Avatar.svelte.d.ts.map +1 -0
- package/dist/components/ui/Badge.svelte +70 -0
- package/dist/components/ui/Badge.svelte.d.ts +12 -0
- package/dist/components/ui/Badge.svelte.d.ts.map +1 -0
- package/dist/components/ui/Button.svelte +226 -0
- package/dist/components/ui/Button.svelte.d.ts +28 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -0
- package/dist/components/ui/Card.svelte +122 -0
- package/dist/components/ui/Card.svelte.d.ts +15 -0
- package/dist/components/ui/Card.svelte.d.ts.map +1 -0
- package/dist/components/ui/Chip.svelte +167 -0
- package/dist/components/ui/Chip.svelte.d.ts +33 -0
- package/dist/components/ui/Chip.svelte.d.ts.map +1 -0
- package/dist/components/ui/Dropdown.svelte +250 -0
- package/dist/components/ui/Dropdown.svelte.d.ts +20 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +294 -0
- package/dist/components/ui/Pagination.svelte.d.ts +21 -0
- package/dist/components/ui/Pagination.svelte.d.ts.map +1 -0
- package/dist/components/ui/Skeleton.svelte +113 -0
- package/dist/components/ui/Skeleton.svelte.d.ts +24 -0
- package/dist/components/ui/Skeleton.svelte.d.ts.map +1 -0
- package/dist/components/ui/Tooltip.svelte +120 -0
- package/dist/components/ui/Tooltip.svelte.d.ts +24 -0
- package/dist/components/ui/Tooltip.svelte.d.ts.map +1 -0
- package/dist/components/ui/Tree.svelte +209 -0
- package/dist/components/ui/Tree.svelte.d.ts +17 -0
- package/dist/components/ui/Tree.svelte.d.ts.map +1 -0
- package/dist/components/ui/__tests__/Badge.test.js +76 -0
- package/dist/components/ui/__tests__/Button.test.js +69 -0
- package/dist/components/ui/__tests__/Card.test.js +103 -0
- package/dist/components/ui/__tests__/Pagination.test.js +99 -0
- package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +112 -0
- package/dist/components/ui/__tests__/gap-primitives.test.js +84 -0
- package/dist/components/ui/index.d.ts +14 -0
- package/dist/components/ui/index.d.ts.map +1 -0
- package/dist/components/ui/index.js +18 -0
- package/dist/i18n/Trans.svelte +29 -0
- package/dist/i18n/Trans.svelte.d.ts +24 -0
- package/dist/i18n/Trans.svelte.d.ts.map +1 -0
- package/dist/i18n/__tests__/i18n.test.js +74 -0
- package/dist/i18n/__tests__/render-parity.spec.js +37 -0
- package/dist/i18n/context.svelte.d.ts +43 -0
- package/dist/i18n/context.svelte.d.ts.map +1 -0
- package/dist/i18n/context.svelte.js +69 -0
- package/dist/i18n/index.d.ts +17 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +24 -0
- package/dist/i18n/registry.d.ts +44 -0
- package/dist/i18n/registry.d.ts.map +1 -0
- package/dist/i18n/registry.js +60 -0
- package/dist/i18n/render.d.ts +22 -0
- package/dist/i18n/render.d.ts.map +1 -0
- package/dist/i18n/render.js +44 -0
- package/dist/i18n/strings.d.ts +7 -0
- package/dist/i18n/strings.d.ts.map +1 -0
- package/dist/i18n/strings.js +19 -0
- package/dist/i18n/strings.ui.d.ts +34 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -0
- package/dist/i18n/strings.ui.js +44 -0
- package/dist/i18n/use-i18n.d.ts +20 -0
- package/dist/i18n/use-i18n.d.ts.map +1 -0
- package/dist/i18n/use-i18n.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/registry/index.d.ts +6 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/registry/module-registry.d.ts +58 -0
- package/dist/registry/module-registry.d.ts.map +1 -0
- package/dist/registry/module-registry.js +94 -0
- package/dist/styles/index.d.ts +4 -0
- package/dist/styles/index.d.ts.map +1 -0
- package/dist/styles/index.js +6 -0
- package/dist/styles/tokens.css +76 -0
- package/dist/test-support/a11y.d.ts +16 -0
- package/dist/test-support/a11y.d.ts.map +1 -0
- package/dist/test-support/a11y.js +32 -0
- package/dist/test-support/setup.d.ts +11 -0
- package/dist/test-support/setup.d.ts.map +1 -0
- package/dist/test-support/setup.js +33 -0
- package/dist/theme/ThemeProvider.svelte +207 -0
- package/dist/theme/ThemeProvider.svelte.d.ts +22 -0
- package/dist/theme/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/theme/context.d.ts +49 -0
- package/dist/theme/context.d.ts.map +1 -0
- package/dist/theme/context.js +32 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +9 -0
- package/dist/theme/tokens.d.ts +309 -0
- package/dist/theme/tokens.d.ts.map +1 -0
- package/dist/theme/tokens.js +418 -0
- package/dist/themes/CUSTOM_THEME_GUIDE.md +341 -0
- package/dist/themes/README.md +675 -0
- package/dist/themes/ThemeProvider.svelte +275 -0
- package/dist/themes/ThemeProvider.svelte.d.ts +24 -0
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/themes/__tests__/css-generator.test.js +32 -0
- package/dist/themes/__tests__/registry.test.js +43 -0
- package/dist/themes/__tests__/token-aliases.test.js +176 -0
- package/dist/themes/components/ColorSchemeToggle.svelte +205 -0
- package/dist/themes/components/ColorSchemeToggle.svelte.d.ts +14 -0
- package/dist/themes/components/ColorSchemeToggle.svelte.d.ts.map +1 -0
- package/dist/themes/components/ThemeSwitcher.svelte +188 -0
- package/dist/themes/components/ThemeSwitcher.svelte.d.ts +14 -0
- package/dist/themes/components/ThemeSwitcher.svelte.d.ts.map +1 -0
- package/dist/themes/components/index.d.ts +8 -0
- package/dist/themes/components/index.d.ts.map +1 -0
- package/dist/themes/components/index.js +7 -0
- package/dist/themes/context.svelte.d.ts +30 -0
- package/dist/themes/context.svelte.d.ts.map +1 -0
- package/dist/themes/context.svelte.js +42 -0
- package/dist/themes/create-theme.d.ts +99 -0
- package/dist/themes/create-theme.d.ts.map +1 -0
- package/dist/themes/create-theme.js +389 -0
- package/dist/themes/css-generator.d.ts +44 -0
- package/dist/themes/css-generator.d.ts.map +1 -0
- package/dist/themes/css-generator.js +226 -0
- package/dist/themes/glass/index.d.ts +14 -0
- package/dist/themes/glass/index.d.ts.map +1 -0
- package/dist/themes/glass/index.js +286 -0
- package/dist/themes/index.d.ts +31 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +37 -0
- package/dist/themes/material/index.d.ts +13 -0
- package/dist/themes/material/index.d.ts.map +1 -0
- package/dist/themes/material/index.js +269 -0
- package/dist/themes/registry.d.ts +64 -0
- package/dist/themes/registry.d.ts.map +1 -0
- package/dist/themes/registry.js +122 -0
- package/dist/themes/shared.d.ts +78 -0
- package/dist/themes/shared.d.ts.map +1 -0
- package/dist/themes/shared.js +179 -0
- package/dist/themes/studio/index.d.ts +14 -0
- package/dist/themes/studio/index.d.ts.map +1 -0
- package/dist/themes/studio/index.js +270 -0
- package/dist/themes/styles/all.css +12 -0
- package/dist/themes/styles/glass.css +432 -0
- package/dist/themes/styles/index.d.ts +22 -0
- package/dist/themes/styles/index.d.ts.map +1 -0
- package/dist/themes/styles/index.js +23 -0
- package/dist/themes/styles/material.css +364 -0
- package/dist/themes/styles/studio.css +416 -0
- package/dist/themes/types.d.ts +273 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +15 -0
- package/dist/types-generic.d.ts +75 -0
- package/dist/types-generic.d.ts.map +1 -0
- package/dist/types-generic.js +1 -0
- package/dist/utils/forms/__tests__/formatters.test.js +27 -0
- package/dist/utils/forms/formatters.d.ts +14 -0
- package/dist/utils/forms/formatters.d.ts.map +1 -0
- package/dist/utils/forms/formatters.js +77 -0
- package/dist/utils/import-optional.d.ts +5 -0
- package/dist/utils/import-optional.d.ts.map +1 -0
- package/dist/utils/import-optional.js +7 -0
- package/dist/utils/theme/__tests__/color.test.js +72 -0
- package/dist/utils/theme/__tests__/typography.test.js +11 -0
- package/dist/utils/theme/color.d.ts +70 -0
- package/dist/utils/theme/color.d.ts.map +1 -0
- package/dist/utils/theme/color.js +221 -0
- package/dist/utils/theme/typography.d.ts +27 -0
- package/dist/utils/theme/typography.d.ts.map +1 -0
- package/dist/utils/theme/typography.js +30 -0
- package/package.json +143 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal - An accessible dialog component
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Native dialog element for accessibility
|
|
6
|
+
* - Backdrop click to close (optional)
|
|
7
|
+
* - Escape key to close
|
|
8
|
+
* - Focus trap
|
|
9
|
+
* - Multiple sizes
|
|
10
|
+
* - Custom header/footer via snippets
|
|
11
|
+
* - Material 3 styling
|
|
12
|
+
*/
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
/** Props for Modal component */
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** Whether the modal is open */
|
|
17
|
+
open?: boolean;
|
|
18
|
+
/** Callback when modal requests to close */
|
|
19
|
+
onClose?: () => void;
|
|
20
|
+
/** Modal title */
|
|
21
|
+
title?: string;
|
|
22
|
+
/** Modal size variant */
|
|
23
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
24
|
+
/** Placement: center (default) or end for a right-anchored full-height drawer */
|
|
25
|
+
placement?: 'center' | 'end';
|
|
26
|
+
/** Whether clicking backdrop closes modal */
|
|
27
|
+
closeOnBackdrop?: boolean;
|
|
28
|
+
/** Whether pressing Escape closes modal */
|
|
29
|
+
closeOnEscape?: boolean;
|
|
30
|
+
/** Show close button */
|
|
31
|
+
showClose?: boolean;
|
|
32
|
+
/** Custom header snippet */
|
|
33
|
+
header?: Snippet;
|
|
34
|
+
/** Custom footer snippet */
|
|
35
|
+
footer?: Snippet;
|
|
36
|
+
/** Main content */
|
|
37
|
+
children?: Snippet;
|
|
38
|
+
/** ARIA label for the dialog */
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
/** ARIA described by ID */
|
|
41
|
+
ariaDescribedBy?: string;
|
|
42
|
+
}
|
|
43
|
+
declare const Modal: import("svelte").Component<Props, {}, "open">;
|
|
44
|
+
type Modal = ReturnType<typeof Modal>;
|
|
45
|
+
export default Modal;
|
|
46
|
+
//# sourceMappingURL=Modal.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/Modal.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,gCAAgC;AAChC,MAAM,WAAW,KAAK;IACpB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,iFAAiF;IACjF,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA6HD,QAAA,MAAM,KAAK,+CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ProgressBar - Visual progress indicator
|
|
4
|
+
* refactored for Material 3
|
|
5
|
+
*
|
|
6
|
+
* Shows progress with optional status-based coloring.
|
|
7
|
+
* Useful for budget tracking, task completion, etc.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { M } from '../../i18n/strings.ui.js';
|
|
11
|
+
import { useI18n } from '../../i18n/use-i18n.js';
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
|
|
15
|
+
/** Props for ProgressBar component */
|
|
16
|
+
export interface Props {
|
|
17
|
+
/** Current value (0-100 or custom range) */
|
|
18
|
+
value: number;
|
|
19
|
+
/** Maximum value (default 100) */
|
|
20
|
+
max?: number;
|
|
21
|
+
/** Status determines color */
|
|
22
|
+
status?: 'default' | 'healthy' | 'warning' | 'critical' | 'over';
|
|
23
|
+
/** Show percentage label */
|
|
24
|
+
showLabel?: boolean;
|
|
25
|
+
/** Custom label text */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Size variant */
|
|
28
|
+
size?: 'sm' | 'md' | 'lg';
|
|
29
|
+
/** Show value over max (e.g., "75/100") */
|
|
30
|
+
showValue?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
value,
|
|
35
|
+
max = 100,
|
|
36
|
+
status = 'default',
|
|
37
|
+
showLabel = false,
|
|
38
|
+
label,
|
|
39
|
+
size = 'md',
|
|
40
|
+
showValue = false,
|
|
41
|
+
}: Props = $props();
|
|
42
|
+
|
|
43
|
+
// Calculate percentage (capped at 100 for display)
|
|
44
|
+
const percentage = $derived(Math.min((value / max) * 100, 100));
|
|
45
|
+
|
|
46
|
+
// Auto-determine status if not provided
|
|
47
|
+
const autoStatus = $derived.by(() => {
|
|
48
|
+
if (status !== 'default') return status;
|
|
49
|
+
const pct = (value / max) * 100;
|
|
50
|
+
if (pct > 100) return 'over';
|
|
51
|
+
if (pct >= 90) return 'critical';
|
|
52
|
+
if (pct >= 75) return 'warning';
|
|
53
|
+
return 'healthy';
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Map status to M3 colors
|
|
57
|
+
const barColorClass = $derived.by(() => {
|
|
58
|
+
switch (autoStatus) {
|
|
59
|
+
case 'healthy':
|
|
60
|
+
return 'color-primary';
|
|
61
|
+
case 'warning':
|
|
62
|
+
return 'color-tertiary';
|
|
63
|
+
case 'critical':
|
|
64
|
+
case 'over':
|
|
65
|
+
return 'color-error';
|
|
66
|
+
default:
|
|
67
|
+
return 'color-primary';
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Format display label
|
|
72
|
+
const displayLabel = $derived.by(() => {
|
|
73
|
+
if (label) return label;
|
|
74
|
+
if (showValue) return `${value.toLocaleString()} / ${max.toLocaleString()}`;
|
|
75
|
+
return `${Math.round(percentage)}%`;
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<div class="progress-container" class:sm={size === 'sm'} class:lg={size === 'lg'}>
|
|
80
|
+
{#if showLabel || showValue}
|
|
81
|
+
<div class="progress-header">
|
|
82
|
+
<span class="progress-label">{displayLabel}</span>
|
|
83
|
+
{#if value > max}
|
|
84
|
+
<span class="over-badge">{t(M['ui.progress_bar.over_by'], { amount: Math.round(value - max).toLocaleString() })}</span>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
89
|
+
<div
|
|
90
|
+
class="progress-track"
|
|
91
|
+
role="progressbar"
|
|
92
|
+
aria-label={label?.trim() ? label : t(M['ui.progress_bar.label'])}
|
|
93
|
+
aria-valuenow={value}
|
|
94
|
+
aria-valuemin={0}
|
|
95
|
+
aria-valuemax={max}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
class="progress-bar {barColorClass}"
|
|
99
|
+
style:width="{percentage}%"
|
|
100
|
+
></div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
.progress-container {
|
|
106
|
+
width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.progress-header {
|
|
110
|
+
display: flex;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
align-items: center;
|
|
113
|
+
margin-bottom: var(--smrt-spacing-2, 8px);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.progress-label {
|
|
117
|
+
font: var(--smrt-typography-label-large-font);
|
|
118
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.sm .progress-label {
|
|
122
|
+
font: var(--smrt-typography-label-medium-font);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.over-badge {
|
|
126
|
+
font: var(--smrt-typography-label-small-font);
|
|
127
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
128
|
+
color: var(--smrt-color-error, #ba1a1a);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.progress-track {
|
|
132
|
+
width: 100%;
|
|
133
|
+
height: 4px;
|
|
134
|
+
background-color: var(--smrt-color-surface-container-highest, #e0e2ec);
|
|
135
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.sm .progress-track {
|
|
140
|
+
height: 2px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.lg .progress-track {
|
|
144
|
+
height: 8px;
|
|
145
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.progress-bar {
|
|
149
|
+
height: 100%;
|
|
150
|
+
transition: width var(--smrt-duration-medium2, 300ms) var(--smrt-easing-standard, cubic-bezier(0.4, 0, 0.2, 1));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (prefers-reduced-motion: reduce) {
|
|
154
|
+
.progress-bar {
|
|
155
|
+
transition: none;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.color-primary { background-color: var(--smrt-color-primary, #005ac1); }
|
|
160
|
+
.color-tertiary { background-color: var(--smrt-color-tertiary, #6b5778); }
|
|
161
|
+
.color-error { background-color: var(--smrt-color-error, #ba1a1a); }
|
|
162
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Props for ProgressBar component */
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** Current value (0-100 or custom range) */
|
|
4
|
+
value: number;
|
|
5
|
+
/** Maximum value (default 100) */
|
|
6
|
+
max?: number;
|
|
7
|
+
/** Status determines color */
|
|
8
|
+
status?: 'default' | 'healthy' | 'warning' | 'critical' | 'over';
|
|
9
|
+
/** Show percentage label */
|
|
10
|
+
showLabel?: boolean;
|
|
11
|
+
/** Custom label text */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Size variant */
|
|
14
|
+
size?: 'sm' | 'md' | 'lg';
|
|
15
|
+
/** Show value over max (e.g., "75/100") */
|
|
16
|
+
showValue?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const ProgressBar: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type ProgressBar = ReturnType<typeof ProgressBar>;
|
|
20
|
+
export default ProgressBar;
|
|
21
|
+
//# sourceMappingURL=ProgressBar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgressBar.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ProgressBar.svelte.ts"],"names":[],"mappings":"AAcA,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACjE,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA0ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden test for ConfirmDialog (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* ConfirmDialog renders a backdrop `<div role="dialog" aria-modal="true">` only
|
|
5
|
+
* while `open` is true, with a title, message, and cancel/confirm buttons. It
|
|
6
|
+
* exposes `onconfirm`/`oncancel` callbacks; Escape and backdrop clicks both
|
|
7
|
+
* route to `oncancel`.
|
|
8
|
+
*/
|
|
9
|
+
import { render, screen } from '@testing-library/svelte';
|
|
10
|
+
import userEvent from '@testing-library/user-event';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
13
|
+
import ConfirmDialog from '../ConfirmDialog.svelte';
|
|
14
|
+
const baseProps = {
|
|
15
|
+
open: true,
|
|
16
|
+
title: 'Delete item',
|
|
17
|
+
message: 'This action cannot be undone.',
|
|
18
|
+
};
|
|
19
|
+
describe('ConfirmDialog', () => {
|
|
20
|
+
it('renders a labelled dialog with title and message when open', () => {
|
|
21
|
+
render(ConfirmDialog, { props: baseProps });
|
|
22
|
+
const dialog = screen.getByRole('dialog');
|
|
23
|
+
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
24
|
+
expect(screen.getByRole('heading', { name: 'Delete item' })).toBeInTheDocument();
|
|
25
|
+
expect(dialog).toHaveAccessibleName('Delete item');
|
|
26
|
+
expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
it('renders nothing when closed', () => {
|
|
29
|
+
render(ConfirmDialog, { props: { ...baseProps, open: false } });
|
|
30
|
+
expect(screen.queryByRole('dialog')).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it('uses default Confirm / Cancel button labels', () => {
|
|
33
|
+
render(ConfirmDialog, { props: baseProps });
|
|
34
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
it('honors custom confirm/cancel labels', () => {
|
|
38
|
+
render(ConfirmDialog, {
|
|
39
|
+
props: { ...baseProps, confirmLabel: 'Delete', cancelLabel: 'Keep' },
|
|
40
|
+
});
|
|
41
|
+
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByRole('button', { name: 'Keep' })).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
it('fires onconfirm when the confirm button is clicked', async () => {
|
|
45
|
+
const onconfirm = vi.fn();
|
|
46
|
+
render(ConfirmDialog, { props: { ...baseProps, onconfirm } });
|
|
47
|
+
await userEvent.click(screen.getByRole('button', { name: 'Confirm' }));
|
|
48
|
+
expect(onconfirm).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
it('fires oncancel when the cancel button is clicked', async () => {
|
|
51
|
+
const oncancel = vi.fn();
|
|
52
|
+
render(ConfirmDialog, { props: { ...baseProps, oncancel } });
|
|
53
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
54
|
+
expect(oncancel).toHaveBeenCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
it('fires oncancel when Escape is pressed', async () => {
|
|
57
|
+
const oncancel = vi.fn();
|
|
58
|
+
render(ConfirmDialog, { props: { ...baseProps, oncancel } });
|
|
59
|
+
screen.getByRole('dialog').focus();
|
|
60
|
+
await userEvent.keyboard('{Escape}');
|
|
61
|
+
expect(oncancel).toHaveBeenCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
it('fires oncancel when the backdrop is clicked', async () => {
|
|
64
|
+
const oncancel = vi.fn();
|
|
65
|
+
render(ConfirmDialog, { props: { ...baseProps, oncancel } });
|
|
66
|
+
await userEvent.click(screen.getByRole('dialog'));
|
|
67
|
+
expect(oncancel).toHaveBeenCalledTimes(1);
|
|
68
|
+
});
|
|
69
|
+
it('does not fire oncancel when clicking inside the dialog content', async () => {
|
|
70
|
+
const oncancel = vi.fn();
|
|
71
|
+
render(ConfirmDialog, { props: { ...baseProps, oncancel } });
|
|
72
|
+
await userEvent.click(screen.getByRole('heading', { name: 'Delete item' }));
|
|
73
|
+
expect(oncancel).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('disables both buttons while loading', () => {
|
|
76
|
+
render(ConfirmDialog, { props: { ...baseProps, loading: true } });
|
|
77
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled();
|
|
78
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled();
|
|
79
|
+
});
|
|
80
|
+
it('does not fire callbacks from disabled buttons while loading', async () => {
|
|
81
|
+
const onconfirm = vi.fn();
|
|
82
|
+
const oncancel = vi.fn();
|
|
83
|
+
render(ConfirmDialog, {
|
|
84
|
+
props: { ...baseProps, loading: true, onconfirm, oncancel },
|
|
85
|
+
});
|
|
86
|
+
await userEvent.click(screen.getByRole('button', { name: 'Confirm' }));
|
|
87
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
88
|
+
expect(onconfirm).not.toHaveBeenCalled();
|
|
89
|
+
expect(oncancel).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
it('applies destructive styling to the confirm button', () => {
|
|
92
|
+
render(ConfirmDialog, {
|
|
93
|
+
props: { ...baseProps, destructive: true, confirmLabel: 'Delete' },
|
|
94
|
+
});
|
|
95
|
+
expect(screen.getByRole('button', { name: 'Delete' })).toHaveClass('destructive');
|
|
96
|
+
});
|
|
97
|
+
it('omits destructive styling by default', () => {
|
|
98
|
+
render(ConfirmDialog, { props: baseProps });
|
|
99
|
+
expect(screen.getByRole('button', { name: 'Confirm' })).not.toHaveClass('destructive');
|
|
100
|
+
});
|
|
101
|
+
it('is axe-clean while open', async () => {
|
|
102
|
+
const { container } = render(ConfirmDialog, { props: baseProps });
|
|
103
|
+
await expectNoA11yViolations(container);
|
|
104
|
+
});
|
|
105
|
+
it('is axe-clean in the destructive loading state', async () => {
|
|
106
|
+
const { container } = render(ConfirmDialog, {
|
|
107
|
+
props: { ...baseProps, destructive: true, loading: true },
|
|
108
|
+
});
|
|
109
|
+
await expectNoA11yViolations(container);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden test for LoadingOverlay (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* LoadingOverlay renders a full-screen `role="dialog"` busy surface only while
|
|
5
|
+
* `show` is true and not dismissed. It carries `aria-busy` (true unless an
|
|
6
|
+
* error is present), an `aria-labelledby` title from `message`, optional
|
|
7
|
+
* progress/items, an error message, and a dismiss button when `dismissible`.
|
|
8
|
+
*/
|
|
9
|
+
import { render, screen } from '@testing-library/svelte';
|
|
10
|
+
import userEvent from '@testing-library/user-event';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
13
|
+
import LoadingOverlay from '../LoadingOverlay.svelte';
|
|
14
|
+
describe('LoadingOverlay', () => {
|
|
15
|
+
it('renders nothing when not shown', () => {
|
|
16
|
+
render(LoadingOverlay, { props: { show: false } });
|
|
17
|
+
expect(screen.queryByRole('dialog')).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
it('renders a busy dialog labelled by its message when shown', () => {
|
|
20
|
+
render(LoadingOverlay, {
|
|
21
|
+
props: { show: true, message: 'Importing data' },
|
|
22
|
+
});
|
|
23
|
+
const dialog = screen.getByRole('dialog');
|
|
24
|
+
expect(dialog).toHaveAttribute('aria-busy', 'true');
|
|
25
|
+
expect(dialog).toHaveAccessibleName('Importing data');
|
|
26
|
+
expect(screen.getByRole('heading', { name: 'Importing data' })).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
it('uses a default message when none is provided', () => {
|
|
29
|
+
render(LoadingOverlay, { props: { show: true } });
|
|
30
|
+
expect(screen.getByRole('heading', { name: 'Loading...' })).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
it('shows a progress percentage when progress > 0', () => {
|
|
33
|
+
render(LoadingOverlay, { props: { show: true, progress: 60 } });
|
|
34
|
+
expect(screen.getByText('60%')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
it('omits the progress readout when progress is 0', () => {
|
|
37
|
+
render(LoadingOverlay, { props: { show: true, progress: 0 } });
|
|
38
|
+
expect(screen.queryByText('0%')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('renders completed items', () => {
|
|
41
|
+
render(LoadingOverlay, {
|
|
42
|
+
props: { show: true, items: ['Profiles', 'Assets'] },
|
|
43
|
+
});
|
|
44
|
+
expect(screen.getByText('Profiles')).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText('Assets')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
it('clears aria-busy and shows the error message in the error state', () => {
|
|
48
|
+
render(LoadingOverlay, {
|
|
49
|
+
props: { show: true, error: { message: 'Import failed' } },
|
|
50
|
+
});
|
|
51
|
+
const dialog = screen.getByRole('dialog');
|
|
52
|
+
expect(dialog).toHaveAttribute('aria-busy', 'false');
|
|
53
|
+
expect(screen.getByText('Import failed')).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
it('omits the dismiss button unless dismissible', () => {
|
|
56
|
+
render(LoadingOverlay, { props: { show: true } });
|
|
57
|
+
expect(screen.queryByRole('button', { name: 'Continue' })).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
it('fires ondismiss and hides the overlay when the dismiss button is clicked', async () => {
|
|
60
|
+
const ondismiss = vi.fn();
|
|
61
|
+
render(LoadingOverlay, {
|
|
62
|
+
props: { show: true, dismissible: true, ondismiss },
|
|
63
|
+
});
|
|
64
|
+
await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
|
|
65
|
+
expect(ondismiss).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(screen.queryByRole('dialog')).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
it('dismisses via the Escape key when dismissible', async () => {
|
|
69
|
+
const ondismiss = vi.fn();
|
|
70
|
+
render(LoadingOverlay, {
|
|
71
|
+
props: { show: true, dismissible: true, ondismiss },
|
|
72
|
+
});
|
|
73
|
+
await userEvent.keyboard('{Escape}');
|
|
74
|
+
expect(ondismiss).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(screen.queryByRole('dialog')).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
it('ignores Escape when not dismissible', async () => {
|
|
78
|
+
render(LoadingOverlay, { props: { show: true, dismissible: false } });
|
|
79
|
+
await userEvent.keyboard('{Escape}');
|
|
80
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
it('is axe-clean in the loading state', async () => {
|
|
83
|
+
const { container } = render(LoadingOverlay, {
|
|
84
|
+
props: { show: true, message: 'Loading your workspace', progress: 45 },
|
|
85
|
+
});
|
|
86
|
+
await expectNoA11yViolations(container);
|
|
87
|
+
});
|
|
88
|
+
it('is axe-clean with completed items and a dismiss action', async () => {
|
|
89
|
+
const { container } = render(LoadingOverlay, {
|
|
90
|
+
props: {
|
|
91
|
+
show: true,
|
|
92
|
+
message: 'Almost done',
|
|
93
|
+
items: ['Step one'],
|
|
94
|
+
dismissible: true,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
await expectNoA11yViolations(container);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden test for Modal (Sweep L4, #1423).
|
|
3
|
+
*
|
|
4
|
+
* Modal wraps a native <dialog>; jsdom implements showModal()/close(), so the
|
|
5
|
+
* open/close effect runs. Content is always in the DOM — visibility is driven
|
|
6
|
+
* by the dialog's modal state.
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen } from '@testing-library/svelte';
|
|
9
|
+
import userEvent from '@testing-library/user-event';
|
|
10
|
+
import { createRawSnippet } from 'svelte';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
13
|
+
import Modal from '../Modal.svelte';
|
|
14
|
+
function bodySnippet(text) {
|
|
15
|
+
return createRawSnippet(() => ({ render: () => `<p>${text}</p>` }));
|
|
16
|
+
}
|
|
17
|
+
describe('Modal', () => {
|
|
18
|
+
it('exposes a dialog labelled by its title, with content', () => {
|
|
19
|
+
render(Modal, {
|
|
20
|
+
props: {
|
|
21
|
+
open: true,
|
|
22
|
+
title: 'Settings',
|
|
23
|
+
children: bodySnippet('Body copy'),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const dialog = screen.getByRole('dialog', { hidden: true });
|
|
27
|
+
expect(dialog).toHaveAttribute('aria-label', 'Settings');
|
|
28
|
+
expect(screen.getByRole('heading', { name: 'Settings', hidden: true })).toBeInTheDocument();
|
|
29
|
+
expect(screen.getByText('Body copy')).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
it('invokes onClose from the close button', async () => {
|
|
32
|
+
const onClose = vi.fn();
|
|
33
|
+
render(Modal, { props: { open: true, title: 'X', onClose } });
|
|
34
|
+
await userEvent.click(screen.getByRole('button', { name: 'Close modal', hidden: true }));
|
|
35
|
+
expect(onClose).toHaveBeenCalledTimes(1);
|
|
36
|
+
});
|
|
37
|
+
it('omits the close button when showClose is false', () => {
|
|
38
|
+
render(Modal, { props: { open: true, title: 'X', showClose: false } });
|
|
39
|
+
expect(screen.queryByRole('button', { name: 'Close modal', hidden: true })).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it('defaults to centered placement', () => {
|
|
42
|
+
render(Modal, { props: { open: true, title: 'X' } });
|
|
43
|
+
expect(screen.getByRole('dialog', { hidden: true })).toHaveClass('modal--center');
|
|
44
|
+
});
|
|
45
|
+
it('applies the end (drawer) placement variant', () => {
|
|
46
|
+
render(Modal, { props: { open: true, title: 'X', placement: 'end' } });
|
|
47
|
+
const dialog = screen.getByRole('dialog', { hidden: true });
|
|
48
|
+
expect(dialog).toHaveClass('modal--end');
|
|
49
|
+
expect(dialog).not.toHaveClass('modal--center');
|
|
50
|
+
});
|
|
51
|
+
it('is axe-clean', async () => {
|
|
52
|
+
const { container } = render(Modal, {
|
|
53
|
+
props: {
|
|
54
|
+
open: true,
|
|
55
|
+
title: 'Accessible dialog',
|
|
56
|
+
children: bodySnippet('content'),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
await expectNoA11yViolations(container);
|
|
60
|
+
});
|
|
61
|
+
it('is axe-clean with end placement', async () => {
|
|
62
|
+
const { container } = render(Modal, {
|
|
63
|
+
props: {
|
|
64
|
+
open: true,
|
|
65
|
+
title: 'Detail drawer',
|
|
66
|
+
placement: 'end',
|
|
67
|
+
children: bodySnippet('content'),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
await expectNoA11yViolations(container);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden test for ProgressBar (Sweep S11, #1416).
|
|
3
|
+
*
|
|
4
|
+
* ProgressBar renders a `role="progressbar"` track carrying
|
|
5
|
+
* `aria-valuenow`/`aria-valuemin`/`aria-valuemax` from its `value`/`max` props.
|
|
6
|
+
* An optional header shows a percentage, a value/max pair, or a custom label,
|
|
7
|
+
* plus an "over by" badge when `value` exceeds `max`.
|
|
8
|
+
*/
|
|
9
|
+
import { render, screen } from '@testing-library/svelte';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { expectNoA11yViolations } from '../../../test-support/a11y';
|
|
12
|
+
import ProgressBar from '../ProgressBar.svelte';
|
|
13
|
+
describe('ProgressBar', () => {
|
|
14
|
+
it('gives the progressbar an accessible name (default and custom label)', async () => {
|
|
15
|
+
const { rerender } = render(ProgressBar, { props: { value: 40 } });
|
|
16
|
+
// Default accessible name comes from the i18n'd "Progress" label.
|
|
17
|
+
expect(screen.getByRole('progressbar', { name: 'Progress' })).toBeInTheDocument();
|
|
18
|
+
// rerender resolves the prop update asynchronously — await before querying.
|
|
19
|
+
await rerender({ value: 40, label: 'Upload' });
|
|
20
|
+
expect(screen.getByRole('progressbar', { name: 'Upload' })).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
it('falls back to the default name when label is empty or whitespace', () => {
|
|
23
|
+
render(ProgressBar, { props: { value: 40, label: ' ' } });
|
|
24
|
+
expect(screen.getByRole('progressbar', { name: 'Progress' })).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('exposes progressbar role with aria-value attributes', () => {
|
|
27
|
+
render(ProgressBar, { props: { value: 40 } });
|
|
28
|
+
const bar = screen.getByRole('progressbar');
|
|
29
|
+
expect(bar).toHaveAttribute('aria-valuenow', '40');
|
|
30
|
+
expect(bar).toHaveAttribute('aria-valuemin', '0');
|
|
31
|
+
expect(bar).toHaveAttribute('aria-valuemax', '100');
|
|
32
|
+
});
|
|
33
|
+
it('reflects a custom max in aria-valuemax', () => {
|
|
34
|
+
render(ProgressBar, { props: { value: 30, max: 200 } });
|
|
35
|
+
const bar = screen.getByRole('progressbar');
|
|
36
|
+
expect(bar).toHaveAttribute('aria-valuenow', '30');
|
|
37
|
+
expect(bar).toHaveAttribute('aria-valuemax', '200');
|
|
38
|
+
});
|
|
39
|
+
it('does not render a label by default', () => {
|
|
40
|
+
render(ProgressBar, { props: { value: 25 } });
|
|
41
|
+
expect(screen.queryByText('25%')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it('shows a rounded percentage label when showLabel is set', () => {
|
|
44
|
+
render(ProgressBar, { props: { value: 33, showLabel: true } });
|
|
45
|
+
expect(screen.getByText('33%')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
it('shows a value / max label when showValue is set', () => {
|
|
48
|
+
render(ProgressBar, { props: { value: 75, max: 100, showValue: true } });
|
|
49
|
+
expect(screen.getByText('75 / 100')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
it('prefers a custom label over percentage and value', () => {
|
|
52
|
+
render(ProgressBar, {
|
|
53
|
+
props: { value: 50, showLabel: true, label: 'Almost there' },
|
|
54
|
+
});
|
|
55
|
+
expect(screen.getByText('Almost there')).toBeInTheDocument();
|
|
56
|
+
expect(screen.queryByText('50%')).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
it('caps aria-valuenow display semantics but reports the raw over-budget value', () => {
|
|
59
|
+
render(ProgressBar, { props: { value: 120, max: 100, showLabel: true } });
|
|
60
|
+
const bar = screen.getByRole('progressbar');
|
|
61
|
+
expect(bar).toHaveAttribute('aria-valuenow', '120');
|
|
62
|
+
expect(bar).toHaveAttribute('aria-valuemax', '100');
|
|
63
|
+
expect(screen.getByText('Over by 20')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
it('is axe-clean at 0%', async () => {
|
|
66
|
+
const { container } = render(ProgressBar, {
|
|
67
|
+
props: { value: 0, showLabel: true },
|
|
68
|
+
});
|
|
69
|
+
await expectNoA11yViolations(container);
|
|
70
|
+
});
|
|
71
|
+
it('is axe-clean mid-progress', async () => {
|
|
72
|
+
const { container } = render(ProgressBar, {
|
|
73
|
+
props: { value: 50, showLabel: true },
|
|
74
|
+
});
|
|
75
|
+
await expectNoA11yViolations(container);
|
|
76
|
+
});
|
|
77
|
+
it('is axe-clean at 100%', async () => {
|
|
78
|
+
const { container } = render(ProgressBar, {
|
|
79
|
+
props: { value: 100, showLabel: true },
|
|
80
|
+
});
|
|
81
|
+
await expectNoA11yViolations(container);
|
|
82
|
+
});
|
|
83
|
+
it('is axe-clean in the over-budget state', async () => {
|
|
84
|
+
const { container } = render(ProgressBar, {
|
|
85
|
+
props: { value: 130, max: 100, showValue: true },
|
|
86
|
+
});
|
|
87
|
+
await expectNoA11yViolations(container);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback components - User feedback and status indicators
|
|
3
|
+
*/
|
|
4
|
+
export { default as ConfirmDialog } from './ConfirmDialog.svelte';
|
|
5
|
+
export { default as LoadingOverlay } from './LoadingOverlay.svelte';
|
|
6
|
+
export { default as Modal } from './Modal.svelte';
|
|
7
|
+
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback components - User feedback and status indicators
|
|
3
|
+
*/
|
|
4
|
+
export { default as ConfirmDialog } from './ConfirmDialog.svelte';
|
|
5
|
+
export { default as LoadingOverlay } from './LoadingOverlay.svelte';
|
|
6
|
+
export { default as Modal } from './Modal.svelte';
|
|
7
|
+
export { default as ProgressBar } from './ProgressBar.svelte';
|
|
8
|
+
// Note: Component prop types (ConfirmDialogProps, etc.) are available
|
|
9
|
+
// via svelte-package build output but cannot be re-exported here because
|
|
10
|
+
// tsc --noEmit cannot resolve type exports from .svelte files.
|