zabi-components 7.0.1 → 8.0.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/README.md +46 -3
- package/THEMING.md +255 -15
- package/dist/atoms/ActionPanel.svelte +12 -7
- package/dist/atoms/ActionPanel.svelte.d.ts +2 -0
- package/dist/atoms/Badge.svelte +93 -56
- package/dist/atoms/Badge.svelte.d.ts +15 -3
- package/dist/atoms/Button.svelte +49 -49
- package/dist/atoms/Button.svelte.d.ts +3 -0
- package/dist/atoms/Card.svelte +31 -28
- package/dist/atoms/Card.svelte.d.ts +5 -2
- package/dist/atoms/CardContent.svelte +10 -2
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardFooter.svelte +10 -2
- package/dist/atoms/CardFooter.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +10 -2
- package/dist/atoms/CardHeader.svelte.d.ts +2 -0
- package/dist/atoms/Checkbox.svelte +4 -0
- package/dist/atoms/Checkbox.svelte.d.ts +2 -0
- package/dist/atoms/CodeBlock.svelte +15 -7
- package/dist/atoms/CodeBlock.svelte.d.ts +9 -3
- package/dist/atoms/ColorPicker.svelte +19 -9
- package/dist/atoms/ColorPicker.svelte.d.ts +3 -1
- package/dist/atoms/Container.svelte +2 -3
- package/dist/atoms/Divider.svelte +4 -4
- package/dist/atoms/FeatureCard.svelte +4 -3
- package/dist/atoms/Heading.svelte +56 -27
- package/dist/atoms/Heading.svelte.d.ts +17 -4
- package/dist/atoms/IconButton.svelte +30 -37
- package/dist/atoms/Input.svelte +26 -34
- package/dist/atoms/List.svelte +10 -2
- package/dist/atoms/List.svelte.d.ts +2 -0
- package/dist/atoms/ListItem.svelte +2 -2
- package/dist/atoms/ListItemLeading.svelte +4 -2
- package/dist/atoms/ListItemLeading.svelte.d.ts +1 -1
- package/dist/atoms/OptimizedImage.svelte +11 -2
- package/dist/atoms/OptimizedImage.svelte.d.ts +2 -0
- package/dist/atoms/Progress.svelte +4 -1
- package/dist/atoms/Progress.svelte.d.ts +2 -0
- package/dist/atoms/Radio.svelte +4 -0
- package/dist/atoms/Radio.svelte.d.ts +2 -0
- package/dist/atoms/Select.svelte +65 -67
- package/dist/atoms/Select.svelte.d.ts +4 -0
- package/dist/atoms/SelectionControl.svelte +5 -1
- package/dist/atoms/SelectionControl.svelte.d.ts +2 -0
- package/dist/atoms/Skeleton.svelte +5 -3
- package/dist/atoms/Table.svelte +2 -1
- package/dist/atoms/Text.svelte +34 -5
- package/dist/atoms/Text.svelte.d.ts +7 -1
- package/dist/atoms/Textarea.svelte +7 -6
- package/dist/atoms/ThemeToggle.svelte +17 -14
- package/dist/atoms/ThemeToggle.svelte.d.ts +2 -0
- package/dist/atoms/Toast.svelte +10 -8
- package/dist/atoms/Toggle.svelte +21 -13
- package/dist/atoms/Toggle.svelte.d.ts +7 -0
- package/dist/atoms/Tooltip.svelte +89 -57
- package/dist/atoms/Tooltip.svelte.d.ts +15 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +1 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/util/cn.d.ts +8 -0
- package/dist/components/util/cn.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +4 -4
- package/dist/components/util/ssr-safe.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -6
- package/dist/molecules/Alert.svelte +43 -64
- package/dist/molecules/Alert.svelte.d.ts +7 -1
- package/dist/molecules/ComponentDemo.svelte +10 -2
- package/dist/molecules/ComponentDemo.svelte.d.ts +2 -0
- package/dist/molecules/ContactForm.svelte +12 -2
- package/dist/molecules/ContactForm.svelte.d.ts +2 -0
- package/dist/molecules/Dropdown.svelte +40 -11
- package/dist/molecules/Dropdown.svelte.d.ts +4 -0
- package/dist/molecules/EmptyState.svelte +2 -1
- package/dist/molecules/Form.svelte +25 -14
- package/dist/molecules/Form.svelte.d.ts +13 -5
- package/dist/molecules/FormField.svelte +2 -1
- package/dist/molecules/Header.svelte +13 -3
- package/dist/molecules/Header.svelte.d.ts +2 -0
- package/dist/molecules/ImageUpload.svelte +54 -15
- package/dist/molecules/ImageUpload.svelte.d.ts +16 -5
- package/dist/molecules/Modal.svelte +110 -31
- package/dist/molecules/Modal.svelte.d.ts +9 -6
- package/dist/molecules/NavigationMenu.svelte +15 -3
- package/dist/molecules/NavigationMenu.svelte.d.ts +4 -0
- package/dist/molecules/NavigationMenuContent.svelte +36 -38
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuItem.svelte +10 -2
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuLink.svelte +10 -2
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuList.svelte +10 -2
- package/dist/molecules/NavigationMenuList.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +16 -3
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +2 -0
- package/dist/molecules/Page.svelte +8 -2
- package/dist/molecules/Page.svelte.d.ts +2 -0
- package/dist/molecules/PropsTable.svelte +6 -3
- package/dist/molecules/PropsTable.svelte.d.ts +2 -0
- package/dist/molecules/RadioGroup.svelte +5 -1
- package/dist/molecules/RadioGroup.svelte.d.ts +2 -0
- package/dist/molecules/Section.svelte +12 -4
- package/dist/molecules/Section.svelte.d.ts +3 -1
- package/dist/molecules/SidebarBrandHeader.svelte +13 -4
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +2 -0
- package/dist/molecules/SidebarFooter.svelte +12 -4
- package/dist/molecules/SidebarFooter.svelte.d.ts +2 -0
- package/dist/molecules/SidebarNavSection.svelte +12 -4
- package/dist/molecules/SidebarNavSection.svelte.d.ts +2 -0
- package/dist/molecules/SlideUp.svelte +85 -21
- package/dist/molecules/SlideUp.svelte.d.ts +5 -4
- package/dist/molecules/Tabs.svelte +25 -19
- package/dist/molecules/Tabs.svelte.d.ts +8 -4
- package/dist/molecules/Toaster.svelte +3 -2
- package/dist/molecules/ToasterToast.svelte +41 -10
- package/dist/molecules/ToasterToast.svelte.d.ts +2 -0
- package/dist/organisms/SidebarAccountPanel.svelte +4 -0
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarNavigation.svelte +102 -59
- package/dist/organisms/SidebarNavigation.svelte.d.ts +2 -0
- package/dist/organisms/SidebarPanel.svelte +19 -11
- package/dist/organisms/SidebarPanel.svelte.d.ts +2 -0
- package/dist/organisms/SidebarShell.svelte +112 -0
- package/dist/organisms/SidebarShell.svelte.d.ts +52 -0
- package/dist/organisms/TopNavbar.svelte +88 -17
- package/dist/organisms/TopNavbar.svelte.d.ts +2 -0
- package/dist/organisms/index.d.ts +1 -0
- package/dist/organisms/index.js +1 -0
- package/dist/routes/lib/variant-utils.d.ts +26 -0
- package/dist/routes/lib/{variant-utils.ts → variant-utils.js} +15 -46
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +3 -0
- package/dist/util/cn.d.ts +7 -0
- package/dist/util/cn.js +48 -0
- package/dist/util/ssr-safe.d.ts +4 -4
- package/dist/zabi-components-colors.css +3 -437
- package/dist/zabi-components-theme-dark-only.css +223 -103
- package/dist/zabi-components-theme-dark.css +223 -103
- package/dist/zabi-components-theme-only.css +347 -107
- package/dist/zabi-components-theme.css +347 -107
- package/dist/zabi-components.css +953 -843
- package/docs/lucide-icons.md +3 -1
- package/package.json +20 -25
- package/dist/lib/layout-width-tokens.d.ts +0 -17
- package/dist/lib/layout-width-tokens.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Button.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Button.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Input.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Input.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/List.d.ts +0 -3
- package/dist/lib/showcase/component-docs/List.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Modal.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Radio.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +0 -3
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +0 -3
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/_shared.d.ts +0 -12
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +0 -1
- package/dist/lib/showcase/component-docs/index.d.ts +0 -4
- package/dist/lib/showcase/component-docs/index.d.ts.map +0 -1
- package/dist/lib/showcase/components-catalog.d.ts +0 -3
- package/dist/lib/showcase/components-catalog.d.ts.map +0 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +0 -89
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +0 -1
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +0 -15
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +0 -1
- package/dist/routes/lib/focus-utils.d.ts +0 -12
- package/dist/routes/lib/focus-utils.ts +0 -110
- package/dist/routes/lib/ssr-safe.ts +0 -58
- package/scripts/fix-lucide-svelte-icon-dts.js +0 -35
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { Check, TriangleAlert, X, Info, Zap } from "@lucide/svelte";
|
|
2
3
|
import type { ExtendedSemanticVariant } from "../types/variants.js";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
3
5
|
type AlertVisualVariant = Exclude<ExtendedSemanticVariant, "default">;
|
|
4
6
|
|
|
5
7
|
interface Props {
|
|
@@ -7,6 +9,12 @@
|
|
|
7
9
|
title?: string;
|
|
8
10
|
message?: string;
|
|
9
11
|
closable?: boolean;
|
|
12
|
+
/** Visible state; `closable` dismiss sets it to `false`. Supports `bind:open`. */
|
|
13
|
+
open?: boolean;
|
|
14
|
+
/** Shrink to content instead of filling the container. */
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
/** @deprecated use `class` — kept so 7.x call sites keep working. */
|
|
10
18
|
className?: string;
|
|
11
19
|
onclick?: (event: Event) => void;
|
|
12
20
|
}
|
|
@@ -16,6 +24,9 @@
|
|
|
16
24
|
title = "",
|
|
17
25
|
message = "",
|
|
18
26
|
closable = false,
|
|
27
|
+
open = $bindable(true),
|
|
28
|
+
inline = false,
|
|
29
|
+
class: classProp = "",
|
|
19
30
|
className = "",
|
|
20
31
|
onclick,
|
|
21
32
|
children,
|
|
@@ -23,6 +34,7 @@
|
|
|
23
34
|
}: Props & { children?: any } = $props();
|
|
24
35
|
|
|
25
36
|
function handleDismiss(event: MouseEvent) {
|
|
37
|
+
open = false;
|
|
26
38
|
if (onclick) {
|
|
27
39
|
onclick(event);
|
|
28
40
|
}
|
|
@@ -32,13 +44,20 @@
|
|
|
32
44
|
variant === "default" ? "info" : variant,
|
|
33
45
|
);
|
|
34
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A tinted fill plus a tinted edge, not a bare 1px outline on a
|
|
49
|
+
* transparent ground — an outline-only alert on a light page barely
|
|
50
|
+
* registers, and the pale warning and citron borders were close to
|
|
51
|
+
* invisible. The icon carries the family colour at its `-text` step.
|
|
52
|
+
*/
|
|
35
53
|
let alertClasses = $derived({
|
|
36
|
-
info: "
|
|
37
|
-
success: "
|
|
38
|
-
warning: "
|
|
39
|
-
error: "
|
|
40
|
-
neutral: "
|
|
41
|
-
energetic:
|
|
54
|
+
info: "bg-info-subtle border-info-border text-info-text",
|
|
55
|
+
success: "bg-success-subtle border-success-border text-success-text",
|
|
56
|
+
warning: "bg-warning-subtle border-warning-border text-warning-text",
|
|
57
|
+
error: "bg-error-subtle border-error-border text-error-text",
|
|
58
|
+
neutral: "bg-neutral-subtle border-neutral-border text-neutral-text",
|
|
59
|
+
energetic:
|
|
60
|
+
"bg-energetic-subtle border-energetic-border text-energetic-text",
|
|
42
61
|
});
|
|
43
62
|
|
|
44
63
|
let alertRole = $derived(
|
|
@@ -48,83 +67,42 @@
|
|
|
48
67
|
|
|
49
68
|
{#snippet icon()}
|
|
50
69
|
{#if visualVariant === "info"}
|
|
51
|
-
<
|
|
52
|
-
<path
|
|
53
|
-
fill-rule="evenodd"
|
|
54
|
-
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
|
55
|
-
clip-rule="evenodd"
|
|
56
|
-
/>
|
|
57
|
-
</svg>
|
|
70
|
+
<Info size={20} aria-hidden="true" />
|
|
58
71
|
{:else if visualVariant === "success"}
|
|
59
|
-
<
|
|
60
|
-
<path
|
|
61
|
-
fill-rule="evenodd"
|
|
62
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
63
|
-
clip-rule="evenodd"
|
|
64
|
-
/>
|
|
65
|
-
</svg>
|
|
72
|
+
<Check size={20} aria-hidden="true" />
|
|
66
73
|
{:else if visualVariant === "warning"}
|
|
67
|
-
<
|
|
68
|
-
<path
|
|
69
|
-
fill-rule="evenodd"
|
|
70
|
-
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
|
71
|
-
clip-rule="evenodd"
|
|
72
|
-
/>
|
|
73
|
-
</svg>
|
|
74
|
+
<TriangleAlert size={20} aria-hidden="true" />
|
|
74
75
|
{:else if visualVariant === "error"}
|
|
75
|
-
<
|
|
76
|
-
<path
|
|
77
|
-
fill-rule="evenodd"
|
|
78
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
79
|
-
clip-rule="evenodd"
|
|
80
|
-
/>
|
|
81
|
-
</svg>
|
|
76
|
+
<X size={20} aria-hidden="true" />
|
|
82
77
|
{:else if visualVariant === "neutral"}
|
|
83
|
-
<
|
|
84
|
-
<path
|
|
85
|
-
fill-rule="evenodd"
|
|
86
|
-
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM10 9a1 1 0 100 2 1 1 0 000-2z"
|
|
87
|
-
clip-rule="evenodd"
|
|
88
|
-
/>
|
|
89
|
-
</svg>
|
|
78
|
+
<Info size={20} aria-hidden="true" />
|
|
90
79
|
{:else}
|
|
91
|
-
<
|
|
92
|
-
<path d="M11 3L4 14h6l-1 7 7-11h-6l1-7z" />
|
|
93
|
-
</svg>
|
|
80
|
+
<Zap size={20} aria-hidden="true" />
|
|
94
81
|
{/if}
|
|
95
82
|
{/snippet}
|
|
96
83
|
|
|
84
|
+
{#if open}
|
|
97
85
|
<div
|
|
98
|
-
class=
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
class={cn(
|
|
87
|
+
'relative rounded-container p-4 border',
|
|
88
|
+
inline ? 'inline-block' : 'block w-full',
|
|
89
|
+
alertClasses[visualVariant],
|
|
90
|
+
'transition-colors duration-150 motion-reduce:transition-none',
|
|
91
|
+
classProp,
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
101
94
|
role={alertRole}
|
|
102
|
-
aria-live={visualVariant === "success" || visualVariant === "info"
|
|
103
|
-
? "polite"
|
|
104
|
-
: "assertive"}
|
|
105
95
|
aria-atomic="true"
|
|
106
96
|
{...restProps}
|
|
107
97
|
>
|
|
108
98
|
{#if closable}
|
|
109
99
|
<button
|
|
110
100
|
onclick={handleDismiss}
|
|
111
|
-
class="
|
|
101
|
+
class="absolute right-2 top-2 z-10 flex size-6 cursor-pointer items-center justify-center rounded-control text-description transition-colors duration-150 hover:bg-surface-hover hover:text-body motion-reduce:transition-none focus-ring--muted"
|
|
112
102
|
aria-label="Dismiss alert"
|
|
113
103
|
type="button"
|
|
114
104
|
>
|
|
115
|
-
<
|
|
116
|
-
class="w-4 h-4 text-current group-hover:scale-110 transition-transform duration-200 motion-reduce:transition-none"
|
|
117
|
-
fill="none"
|
|
118
|
-
stroke="currentColor"
|
|
119
|
-
viewBox="0 0 24 24"
|
|
120
|
-
>
|
|
121
|
-
<path
|
|
122
|
-
stroke-linecap="round"
|
|
123
|
-
stroke-linejoin="round"
|
|
124
|
-
stroke-width="2"
|
|
125
|
-
d="M6 18L18 6M6 6l12 12"
|
|
126
|
-
/>
|
|
127
|
-
</svg>
|
|
105
|
+
<X size={16} aria-hidden="true" />
|
|
128
106
|
</button>
|
|
129
107
|
{/if}
|
|
130
108
|
|
|
@@ -148,3 +126,4 @@
|
|
|
148
126
|
</div>
|
|
149
127
|
</div>
|
|
150
128
|
</div>
|
|
129
|
+
{/if}
|
|
@@ -4,12 +4,18 @@ interface Props {
|
|
|
4
4
|
title?: string;
|
|
5
5
|
message?: string;
|
|
6
6
|
closable?: boolean;
|
|
7
|
+
/** Visible state; `closable` dismiss sets it to `false`. Supports `bind:open`. */
|
|
8
|
+
open?: boolean;
|
|
9
|
+
/** Shrink to content instead of filling the container. */
|
|
10
|
+
inline?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
/** @deprecated use `class` — kept so 7.x call sites keep working. */
|
|
7
13
|
className?: string;
|
|
8
14
|
onclick?: (event: Event) => void;
|
|
9
15
|
}
|
|
10
16
|
type $$ComponentProps = Props & {
|
|
11
17
|
children?: any;
|
|
12
18
|
};
|
|
13
|
-
declare const Alert: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
19
|
+
declare const Alert: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
14
20
|
type Alert = ReturnType<typeof Alert>;
|
|
15
21
|
export default Alert;
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
import Card from "../atoms/Card.svelte";
|
|
4
4
|
import CardHeader from "../atoms/CardHeader.svelte";
|
|
5
5
|
import CardContent from "../atoms/CardContent.svelte";
|
|
6
|
+
import { cn } from "../util/cn.js";
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
title: string;
|
|
9
10
|
description?: string;
|
|
10
11
|
code: string;
|
|
11
12
|
language?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
/** @deprecated use `class`. */
|
|
12
15
|
className?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -17,11 +20,16 @@
|
|
|
17
20
|
description = "",
|
|
18
21
|
code,
|
|
19
22
|
language = "svelte",
|
|
20
|
-
|
|
23
|
+
class: classAttr = "",
|
|
24
|
+
className: legacyClass = "",
|
|
21
25
|
children,
|
|
22
26
|
...restProps
|
|
23
27
|
} = $props<Props & { children?: any }>();
|
|
24
28
|
|
|
29
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
30
|
+
* Both are merged here so existing call sites keep working. */
|
|
31
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
32
|
+
|
|
25
33
|
let showCode = $state(false);
|
|
26
34
|
</script>
|
|
27
35
|
|
|
@@ -36,7 +44,7 @@
|
|
|
36
44
|
<CardContent>
|
|
37
45
|
<button
|
|
38
46
|
onclick={() => (showCode = !showCode)}
|
|
39
|
-
class="absolute top-8 right-8 z-10 flex cursor-pointer items-center gap-2 rounded-
|
|
47
|
+
class="absolute top-8 right-8 z-10 flex cursor-pointer items-center gap-2 rounded-control px-3 py-2 text-sm text-description transition-colors duration-200 hover:bg-surface-hover hover:text-body"
|
|
40
48
|
aria-label={showCode ? "Show preview" : "Show code"}
|
|
41
49
|
>
|
|
42
50
|
{#if showCode}
|
|
@@ -8,17 +8,25 @@
|
|
|
8
8
|
import CardHeader from "../atoms/CardHeader.svelte";
|
|
9
9
|
import CardContent from "../atoms/CardContent.svelte";
|
|
10
10
|
import type { ContactFormData } from "../types/page.types";
|
|
11
|
+
import { cn } from "../util/cn.js";
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
14
|
+
class?: string;
|
|
15
|
+
/** @deprecated use `class`. */
|
|
13
16
|
className?: string;
|
|
14
17
|
onsubmit?: (event: SubmitEvent) => void;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
let {
|
|
18
|
-
|
|
21
|
+
class: classAttr = "",
|
|
22
|
+
className: legacyClass = "",
|
|
19
23
|
onsubmit,
|
|
20
24
|
}: Props = $props();
|
|
21
25
|
|
|
26
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
27
|
+
* Both are merged here so existing call sites keep working. */
|
|
28
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
29
|
+
|
|
22
30
|
let formData = $state<ContactFormData>({
|
|
23
31
|
name: "",
|
|
24
32
|
email: "",
|
|
@@ -60,6 +68,8 @@
|
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
if (!validate(data)) {
|
|
71
|
+
// Block native submission so the page does not reload and wipe the errors.
|
|
72
|
+
event.preventDefault();
|
|
63
73
|
formErrorMessage =
|
|
64
74
|
"We could not submit your request. Fix the highlighted fields and try again.";
|
|
65
75
|
return;
|
|
@@ -79,7 +89,7 @@
|
|
|
79
89
|
<Form onsubmit={handleFormSubmit} className="space-y-4">
|
|
80
90
|
{#if formErrorMessage}
|
|
81
91
|
<div
|
|
82
|
-
class="rounded-
|
|
92
|
+
class="rounded-control border border-error px-4 py-3 text-sm text-error"
|
|
83
93
|
role="alert"
|
|
84
94
|
>
|
|
85
95
|
<p class="font-medium">Something went wrong</p>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { generateId } from "../util/ssr-safe.js";
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
|
|
5
6
|
export type DropdownTriggerProps = {
|
|
6
7
|
'aria-expanded': boolean;
|
|
@@ -9,6 +10,8 @@
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
13
|
+
/** Extra classes for the host element. */
|
|
14
|
+
class?: string;
|
|
12
15
|
isOpen?: boolean;
|
|
13
16
|
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
14
17
|
ariaLabel?: string;
|
|
@@ -22,10 +25,13 @@
|
|
|
22
25
|
}>;
|
|
23
26
|
onOptionClick?: (value: string | number) => void;
|
|
24
27
|
trigger: Snippet<[DropdownTriggerProps]>;
|
|
28
|
+
/** Rendered in the popup above the menu/listbox, outside its role (e.g. a search field). */
|
|
29
|
+
header?: Snippet;
|
|
25
30
|
children?: Snippet;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
let {
|
|
34
|
+
class: className = "",
|
|
29
35
|
isOpen = $bindable(false),
|
|
30
36
|
placement = 'bottom-start',
|
|
31
37
|
ariaLabel = 'Menu',
|
|
@@ -34,6 +40,7 @@
|
|
|
34
40
|
options = [],
|
|
35
41
|
onOptionClick,
|
|
36
42
|
trigger,
|
|
43
|
+
header,
|
|
37
44
|
children,
|
|
38
45
|
...restProps
|
|
39
46
|
}: Props = $props();
|
|
@@ -49,8 +56,22 @@
|
|
|
49
56
|
'aria-controls': menuId,
|
|
50
57
|
} satisfies DropdownTriggerProps);
|
|
51
58
|
|
|
59
|
+
function isTextEntryTarget(target: EventTarget | null): boolean {
|
|
60
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
61
|
+
if (target.isContentEditable || target instanceof HTMLTextAreaElement) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
target instanceof HTMLInputElement &&
|
|
66
|
+
!['checkbox', 'radio', 'button', 'submit', 'reset'].includes(target.type)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
function handleKeydown(event: KeyboardEvent) {
|
|
53
71
|
if (!isOpen) {
|
|
72
|
+
if (event.key === ' ' && isTextEntryTarget(event.target)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
54
75
|
if (
|
|
55
76
|
event.key === 'Enter' ||
|
|
56
77
|
event.key === ' ' ||
|
|
@@ -77,10 +98,13 @@
|
|
|
77
98
|
focusPreviousItem();
|
|
78
99
|
break;
|
|
79
100
|
case 'Home':
|
|
101
|
+
// Let text fields (e.g. Select search) keep native caret movement.
|
|
102
|
+
if (isTextEntryTarget(event.target)) break;
|
|
80
103
|
event.preventDefault();
|
|
81
104
|
focusFirstItem();
|
|
82
105
|
break;
|
|
83
106
|
case 'End':
|
|
107
|
+
if (isTextEntryTarget(event.target)) break;
|
|
84
108
|
event.preventDefault();
|
|
85
109
|
focusLastItem();
|
|
86
110
|
break;
|
|
@@ -94,9 +118,13 @@
|
|
|
94
118
|
|
|
95
119
|
function getMenuItems(): HTMLElement[] {
|
|
96
120
|
if (!menuElement) return [];
|
|
121
|
+
// `menuitemradio` and `menuitemcheckbox` are menu items too. Matching
|
|
122
|
+
// only `menuitem` left a menu built from them with nothing to focus:
|
|
123
|
+
// arrows and Home/End did nothing, opening by keyboard focused nothing,
|
|
124
|
+
// and Tab closed the menu, so its items could not be reached at all.
|
|
97
125
|
return Array.from(
|
|
98
126
|
menuElement.querySelectorAll<HTMLElement>(
|
|
99
|
-
'[role="menuitem"], [role="option"]',
|
|
127
|
+
'[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"], [role="option"]',
|
|
100
128
|
),
|
|
101
129
|
);
|
|
102
130
|
}
|
|
@@ -182,7 +210,7 @@
|
|
|
182
210
|
const dropdownContentClasses = $derived(() => {
|
|
183
211
|
return [
|
|
184
212
|
placementClasses(),
|
|
185
|
-
'rounded-
|
|
213
|
+
'rounded-control border border-border bg-surface-overlay py-2 shadow-lg transition-all duration-200 ease-in-out',
|
|
186
214
|
transformClasses(),
|
|
187
215
|
]
|
|
188
216
|
.join(' ')
|
|
@@ -193,12 +221,12 @@
|
|
|
193
221
|
const itemRole = $derived(menuRole === 'listbox' ? 'option' : 'menuitem');
|
|
194
222
|
|
|
195
223
|
const optionClasses =
|
|
196
|
-
'focus-ring flex w-full items-center justify-start rounded-
|
|
224
|
+
'focus-ring flex w-full items-center justify-start rounded-control px-3 py-2 text-left text-sm text-body transition-colors hover:bg-surface-overlay-hover focus:outline-none disabled:cursor-not-allowed disabled:opacity-50';
|
|
197
225
|
</script>
|
|
198
226
|
|
|
199
227
|
<div
|
|
200
228
|
bind:this={rootEl}
|
|
201
|
-
class="relative inline-block"
|
|
229
|
+
class={cn("relative inline-block", className)}
|
|
202
230
|
data-placement={placement}
|
|
203
231
|
onkeydown={handleKeydown}
|
|
204
232
|
{...restProps}
|
|
@@ -206,13 +234,13 @@
|
|
|
206
234
|
{@render trigger(triggerAria)}
|
|
207
235
|
|
|
208
236
|
{#if isOpen}
|
|
209
|
-
<div
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
237
|
+
<div bind:this={menuElement} class={dropdownContentClasses()}>
|
|
238
|
+
{@render header?.()}
|
|
239
|
+
<div
|
|
240
|
+
id={menuId}
|
|
241
|
+
role={menuRole === 'listbox' ? 'listbox' : 'menu'}
|
|
242
|
+
aria-label={ariaLabel}
|
|
243
|
+
>
|
|
216
244
|
{#if options.length > 0}
|
|
217
245
|
<div class="px-2 py-1">
|
|
218
246
|
{#each options as option (option.value)}
|
|
@@ -236,6 +264,7 @@
|
|
|
236
264
|
{:else if children}
|
|
237
265
|
{@render children?.()}
|
|
238
266
|
{/if}
|
|
267
|
+
</div>
|
|
239
268
|
</div>
|
|
240
269
|
{/if}
|
|
241
270
|
</div>
|
|
@@ -5,6 +5,8 @@ export type DropdownTriggerProps = {
|
|
|
5
5
|
'aria-controls': string;
|
|
6
6
|
};
|
|
7
7
|
interface Props {
|
|
8
|
+
/** Extra classes for the host element. */
|
|
9
|
+
class?: string;
|
|
8
10
|
isOpen?: boolean;
|
|
9
11
|
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
10
12
|
ariaLabel?: string;
|
|
@@ -18,6 +20,8 @@ interface Props {
|
|
|
18
20
|
}>;
|
|
19
21
|
onOptionClick?: (value: string | number) => void;
|
|
20
22
|
trigger: Snippet<[DropdownTriggerProps]>;
|
|
23
|
+
/** Rendered in the popup above the menu/listbox, outside its role (e.g. a search field). */
|
|
24
|
+
header?: Snippet;
|
|
21
25
|
children?: Snippet;
|
|
22
26
|
}
|
|
23
27
|
declare const Dropdown: import("svelte").Component<Props, {}, "isOpen">;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import { generateId } from "../util/ssr-safe.js";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
title: string;
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
</script>
|
|
23
24
|
|
|
24
25
|
<section
|
|
25
|
-
class="mx-auto flex flex-col items-center justify-center gap-4 px-6 py-12 text-center
|
|
26
|
+
class={cn("mx-auto flex flex-col items-center justify-center gap-4 px-6 py-12 text-center", className)}
|
|
26
27
|
aria-labelledby={titleId}
|
|
27
28
|
>
|
|
28
29
|
{#if media}
|
|
@@ -1,35 +1,46 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLFormAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLFormAttributes, "method" | "action" | "onsubmit" | "class"> & {
|
|
3
7
|
method?: "get" | "post";
|
|
4
8
|
action?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
/** @deprecated use `class`. */
|
|
5
11
|
className?: string;
|
|
12
|
+
/** Skip native constraint validation (e.g. when validating in `onsubmit`). */
|
|
13
|
+
novalidate?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Runs on submit. The form still submits natively unless you call
|
|
16
|
+
* `event.preventDefault()` — do so for client-side handling.
|
|
17
|
+
*/
|
|
6
18
|
onsubmit?: (event: SubmitEvent) => void;
|
|
7
|
-
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
};
|
|
8
21
|
|
|
9
22
|
let {
|
|
10
23
|
method = "post",
|
|
11
24
|
action = "",
|
|
12
|
-
|
|
25
|
+
class: classAttr = "",
|
|
26
|
+
className: legacyClass = "",
|
|
27
|
+
novalidate = false,
|
|
13
28
|
onsubmit,
|
|
14
29
|
children,
|
|
15
30
|
...restProps
|
|
16
|
-
} = $props
|
|
17
|
-
|
|
18
|
-
function handleSubmit(event: SubmitEvent) {
|
|
19
|
-
const formData = new FormData(event.target as HTMLFormElement);
|
|
20
|
-
const data = Object.fromEntries(formData.entries());
|
|
31
|
+
}: Props = $props();
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
33
|
+
/** `class` is the public prop; `className` is a deprecated alias.
|
|
34
|
+
* Both are merged here so existing call sites keep working. */
|
|
35
|
+
const className = $derived(cn(`${classAttr} ${legacyClass}`));
|
|
26
36
|
</script>
|
|
27
37
|
|
|
28
38
|
<form
|
|
29
39
|
{method}
|
|
30
40
|
{action}
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
novalidate={novalidate || undefined}
|
|
42
|
+
class={cn("space-y-4", className)}
|
|
43
|
+
onsubmit={(event) => onsubmit?.(event)}
|
|
33
44
|
{...restProps}
|
|
34
45
|
>
|
|
35
46
|
{@render children?.()}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLFormAttributes } from "svelte/elements";
|
|
3
|
+
type Props = Omit<HTMLFormAttributes, "method" | "action" | "onsubmit" | "class"> & {
|
|
2
4
|
method?: "get" | "post";
|
|
3
5
|
action?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
/** @deprecated use `class`. */
|
|
4
8
|
className?: string;
|
|
9
|
+
/** Skip native constraint validation (e.g. when validating in `onsubmit`). */
|
|
10
|
+
novalidate?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Runs on submit. The form still submits natively unless you call
|
|
13
|
+
* `event.preventDefault()` — do so for client-side handling.
|
|
14
|
+
*/
|
|
5
15
|
onsubmit?: (event: SubmitEvent) => void;
|
|
6
|
-
|
|
7
|
-
type $$ComponentProps = Props & {
|
|
8
|
-
children?: any;
|
|
16
|
+
children?: Snippet;
|
|
9
17
|
};
|
|
10
|
-
declare const Form: import("svelte").Component
|
|
18
|
+
declare const Form: import("svelte").Component<Props, {}, "">;
|
|
11
19
|
type Form = ReturnType<typeof Form>;
|
|
12
20
|
export default Form;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { generateId } from "../util/ssr-safe.js";
|
|
4
4
|
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
5
6
|
export type FormFieldControlProps = {
|
|
6
7
|
id: string;
|
|
7
8
|
'aria-describedby'?: string;
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
);
|
|
48
49
|
</script>
|
|
49
50
|
|
|
50
|
-
<div class={
|
|
51
|
+
<div class={cn('space-y-2', className)}>
|
|
51
52
|
<div class="flex items-baseline justify-between gap-3">
|
|
52
53
|
<label
|
|
53
54
|
class={[
|
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
import Badge from "../atoms/Badge.svelte";
|
|
3
3
|
import Heading from "../atoms/Heading.svelte";
|
|
4
4
|
import type { ComponentCategory } from "../types/page.types";
|
|
5
|
+
import { cn } from "../util/cn.js";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
8
|
+
/** Extra classes for the host element. */
|
|
9
|
+
class?: string;
|
|
7
10
|
title: string;
|
|
8
11
|
description: string;
|
|
9
12
|
category: ComponentCategory;
|
|
10
13
|
variantsStates?: string[];
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
let {
|
|
16
|
+
let {
|
|
17
|
+
class: className = "",
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
category,
|
|
21
|
+
variantsStates,
|
|
22
|
+
...restProps
|
|
23
|
+
}: Props = $props();
|
|
14
24
|
</script>
|
|
15
25
|
|
|
16
|
-
<header class="space-y-4">
|
|
26
|
+
<header class={cn("space-y-4", className)} {...restProps}>
|
|
17
27
|
<div class="space-y-2">
|
|
18
28
|
<Heading level={1} text={title} />
|
|
19
29
|
<p class="text-lg whitespace-pre-line text-description">{description}</p>
|
|
@@ -32,7 +42,7 @@
|
|
|
32
42
|
Variants / states:
|
|
33
43
|
</span>
|
|
34
44
|
{#each variantsStates as v (v)}
|
|
35
|
-
<span class="rounded-
|
|
45
|
+
<span class="rounded-control bg-base-100 px-2 py-1 text-xs text-headline">
|
|
36
46
|
{v}
|
|
37
47
|
</span>
|
|
38
48
|
{/each}
|