@nqmcreative/ui 0.1.1 → 0.2.1
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 +89 -34
- package/cli/index.mjs +83 -33
- package/dist/core/calendar.d.ts +3 -0
- package/dist/core/calendar.js +36 -0
- package/dist/core/dialog.svelte.d.ts +14 -0
- package/dist/core/dialog.svelte.js +23 -0
- package/dist/core/files.d.ts +35 -0
- package/dist/core/files.js +62 -0
- package/dist/core/index.d.ts +37 -0
- package/dist/core/index.js +35 -0
- package/dist/core/list.svelte.d.ts +41 -0
- package/dist/core/list.svelte.js +77 -0
- package/dist/core/number.d.ts +7 -0
- package/dist/core/number.js +17 -0
- package/dist/core/pagination.d.ts +6 -0
- package/dist/core/pagination.js +21 -0
- package/dist/core/password.d.ts +8 -0
- package/dist/core/password.js +23 -0
- package/dist/core/table.d.ts +22 -0
- package/dist/core/table.js +41 -0
- package/dist/core/theme.svelte.d.ts +16 -0
- package/dist/core/theme.svelte.js +34 -0
- package/dist/core/trigger.d.ts +35 -0
- package/dist/core/trigger.js +77 -0
- package/dist/index.d.ts +11 -103
- package/dist/index.js +11 -75
- package/dist/{components → styles/matte}/AccordionItem.svelte +1 -1
- package/dist/{components → styles/matte}/AccordionItem.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Alert.svelte +8 -2
- package/dist/{components → styles/matte}/Alert.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Avatar.svelte +1 -1
- package/dist/{components → styles/matte}/Avatar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Badge.svelte +8 -3
- package/dist/{components → styles/matte}/Badge.svelte.d.ts +1 -3
- package/dist/{components → styles/matte}/Breadcrumb.svelte +2 -2
- package/dist/{components → styles/matte}/Breadcrumb.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Button.svelte +1 -1
- package/dist/{components → styles/matte}/Button.svelte.d.ts +1 -1
- package/dist/styles/matte/CTASection.svelte +76 -0
- package/dist/styles/matte/CTASection.svelte.d.ts +18 -0
- package/dist/{components → styles/matte}/Calendar.svelte +7 -17
- package/dist/{components → styles/matte}/Calendar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Card.svelte +1 -1
- package/dist/{components → styles/matte}/Card.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Checkbox.svelte +1 -1
- package/dist/{components → styles/matte}/Checkbox.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/CheckboxGroup.svelte +1 -1
- package/dist/{components → styles/matte}/CheckboxGroup.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Combobox.svelte +31 -42
- package/dist/{components → styles/matte}/Combobox.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/CommandPalette.svelte +26 -41
- package/dist/{components → styles/matte}/CommandPalette.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/ConfirmDialog.svelte +2 -2
- package/dist/{components → styles/matte}/ConfirmDialog.svelte.d.ts +2 -2
- package/dist/{components → styles/matte}/ContextMenu.svelte +5 -7
- package/dist/{components → styles/matte}/DatePicker.svelte +7 -11
- package/dist/{components → styles/matte}/DatePicker.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Drawer.svelte +4 -7
- package/dist/{components → styles/matte}/Dropdown.svelte +18 -36
- package/dist/{components → styles/matte}/Dropdown.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Dropzone.svelte +11 -43
- package/dist/{components → styles/matte}/Dropzone.svelte.d.ts +2 -5
- package/dist/{components → styles/matte}/EmptyState.svelte +1 -1
- package/dist/{components → styles/matte}/EmptyState.svelte.d.ts +1 -1
- package/dist/styles/matte/FeatureCard.svelte +58 -0
- package/dist/styles/matte/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/matte/FeatureGrid.svelte +62 -0
- package/dist/styles/matte/FeatureGrid.svelte.d.ts +16 -0
- package/dist/{components → styles/matte}/Footer.svelte +1 -1
- package/dist/{components → styles/matte}/Footer.svelte.d.ts +1 -1
- package/dist/styles/matte/HeroSection.svelte +80 -0
- package/dist/styles/matte/HeroSection.svelte.d.ts +21 -0
- package/dist/{components → styles/matte}/Input.svelte +1 -1
- package/dist/{components → styles/matte}/Input.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Link.svelte +1 -1
- package/dist/{components → styles/matte}/Link.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/LocaleProvider.svelte +1 -1
- package/dist/{components → styles/matte}/LocaleProvider.svelte.d.ts +1 -1
- package/dist/styles/matte/LogoCloud.svelte +61 -0
- package/dist/styles/matte/LogoCloud.svelte.d.ts +19 -0
- package/dist/{components → styles/matte}/MenuItem.svelte +1 -1
- package/dist/{components → styles/matte}/MenuItem.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Modal.svelte +4 -7
- package/dist/{components → styles/matte}/MultiSelect.svelte +18 -37
- package/dist/{components → styles/matte}/MultiSelect.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Navbar.svelte +2 -2
- package/dist/{components → styles/matte}/Navbar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/NumberInput.svelte +4 -8
- package/dist/{components → styles/matte}/NumberInput.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Pagination.svelte +4 -15
- package/dist/{components → styles/matte}/Pagination.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/PasswordInput.svelte +4 -13
- package/dist/{components → styles/matte}/PasswordInput.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Popover.svelte +16 -41
- package/dist/{components → styles/matte}/Popover.svelte.d.ts +1 -1
- package/dist/styles/matte/PricingCard.svelte +107 -0
- package/dist/styles/matte/PricingCard.svelte.d.ts +25 -0
- package/dist/{components → styles/matte}/Progress.svelte +3 -2
- package/dist/{components → styles/matte}/Progress.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Radio.svelte +1 -1
- package/dist/{components → styles/matte}/Radio.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/RadioGroup.svelte +1 -1
- package/dist/{components → styles/matte}/RadioGroup.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/SegmentedControl.svelte +1 -1
- package/dist/{components → styles/matte}/SegmentedControl.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Select.svelte +1 -1
- package/dist/{components → styles/matte}/Select.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Sidebar.svelte +2 -2
- package/dist/{components → styles/matte}/Sidebar.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Slider.svelte +3 -2
- package/dist/{components → styles/matte}/Slider.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Spinner.svelte +2 -2
- package/dist/{components → styles/matte}/Spinner.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Stat.svelte +1 -1
- package/dist/{components → styles/matte}/Stat.svelte.d.ts +1 -1
- package/dist/styles/matte/StatsBand.svelte +57 -0
- package/dist/styles/matte/StatsBand.svelte.d.ts +20 -0
- package/dist/{components → styles/matte}/Steps.svelte +1 -1
- package/dist/{components → styles/matte}/Steps.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Switch.svelte +1 -1
- package/dist/{components → styles/matte}/Switch.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/Table.svelte +6 -28
- package/dist/{components → styles/matte}/Table.svelte.d.ts +2 -6
- package/dist/{components → styles/matte}/Tabs.svelte +8 -1
- package/dist/{components → styles/matte}/Tabs.svelte.d.ts +1 -1
- package/dist/styles/matte/Testimonial.svelte +67 -0
- package/dist/styles/matte/Testimonial.svelte.d.ts +19 -0
- package/dist/{components → styles/matte}/Textarea.svelte +1 -1
- package/dist/{components → styles/matte}/Textarea.svelte.d.ts +1 -1
- package/dist/{components → styles/matte}/ThemeToggle.svelte +14 -27
- package/dist/{components → styles/matte}/ThemeToggle.svelte.d.ts +2 -6
- package/dist/{components → styles/matte}/Toaster.svelte +4 -4
- package/dist/styles/matte/index.d.ts +109 -0
- package/dist/styles/matte/index.js +83 -0
- package/dist/styles/paper/Accordion.svelte +17 -0
- package/dist/styles/paper/Accordion.svelte.d.ts +8 -0
- package/dist/styles/paper/AccordionItem.svelte +46 -0
- package/dist/styles/paper/AccordionItem.svelte.d.ts +14 -0
- package/dist/styles/paper/Alert.svelte +99 -0
- package/dist/styles/paper/Alert.svelte.d.ts +18 -0
- package/dist/styles/paper/Avatar.svelte +60 -0
- package/dist/styles/paper/Avatar.svelte.d.ts +16 -0
- package/dist/styles/paper/AvatarGroup.svelte +17 -0
- package/dist/styles/paper/AvatarGroup.svelte.d.ts +8 -0
- package/dist/styles/paper/Badge.svelte +58 -0
- package/dist/styles/paper/Badge.svelte.d.ts +16 -0
- package/dist/styles/paper/Breadcrumb.svelte +49 -0
- package/dist/styles/paper/Breadcrumb.svelte.d.ts +14 -0
- package/dist/styles/paper/Button.svelte +96 -0
- package/dist/styles/paper/Button.svelte.d.ts +21 -0
- package/dist/styles/paper/CTASection.svelte +76 -0
- package/dist/styles/paper/CTASection.svelte.d.ts +18 -0
- package/dist/styles/paper/Calendar.svelte +202 -0
- package/dist/styles/paper/Calendar.svelte.d.ts +22 -0
- package/dist/styles/paper/Card.svelte +78 -0
- package/dist/styles/paper/Card.svelte.d.ts +21 -0
- package/dist/styles/paper/Checkbox.svelte +75 -0
- package/dist/styles/paper/Checkbox.svelte.d.ts +15 -0
- package/dist/styles/paper/CheckboxGroup.svelte +111 -0
- package/dist/styles/paper/CheckboxGroup.svelte.d.ts +30 -0
- package/dist/styles/paper/Combobox.svelte +266 -0
- package/dist/styles/paper/Combobox.svelte.d.ts +29 -0
- package/dist/styles/paper/CommandPalette.svelte +209 -0
- package/dist/styles/paper/CommandPalette.svelte.d.ts +32 -0
- package/dist/styles/paper/ConfirmDialog.svelte +97 -0
- package/dist/styles/paper/ConfirmDialog.svelte.d.ts +22 -0
- package/dist/styles/paper/ContextMenu.svelte +88 -0
- package/dist/styles/paper/ContextMenu.svelte.d.ts +15 -0
- package/dist/styles/paper/DatePicker.svelte +192 -0
- package/dist/styles/paper/DatePicker.svelte.d.ts +23 -0
- package/dist/styles/paper/Divider.svelte +30 -0
- package/dist/styles/paper/Divider.svelte.d.ts +10 -0
- package/dist/styles/paper/Drawer.svelte +165 -0
- package/dist/styles/paper/Drawer.svelte.d.ts +20 -0
- package/dist/styles/paper/Dropdown.svelte +94 -0
- package/dist/styles/paper/Dropdown.svelte.d.ts +23 -0
- package/dist/styles/paper/Dropzone.svelte +210 -0
- package/dist/styles/paper/Dropzone.svelte.d.ts +29 -0
- package/dist/styles/paper/EmptyState.svelte +71 -0
- package/dist/styles/paper/EmptyState.svelte.d.ts +18 -0
- package/dist/styles/paper/FeatureCard.svelte +58 -0
- package/dist/styles/paper/FeatureCard.svelte.d.ts +16 -0
- package/dist/styles/paper/FeatureGrid.svelte +62 -0
- package/dist/styles/paper/FeatureGrid.svelte.d.ts +16 -0
- package/dist/styles/paper/Field.svelte +42 -0
- package/dist/styles/paper/Field.svelte.d.ts +16 -0
- package/dist/styles/paper/Footer.svelte +91 -0
- package/dist/styles/paper/Footer.svelte.d.ts +25 -0
- package/dist/styles/paper/HeroSection.svelte +86 -0
- package/dist/styles/paper/HeroSection.svelte.d.ts +21 -0
- package/dist/styles/paper/Input.svelte +60 -0
- package/dist/styles/paper/Input.svelte.d.ts +18 -0
- package/dist/styles/paper/InputGroup.svelte +20 -0
- package/dist/styles/paper/InputGroup.svelte.d.ts +11 -0
- package/dist/styles/paper/Kbd.svelte +17 -0
- package/dist/styles/paper/Kbd.svelte.d.ts +8 -0
- package/dist/styles/paper/Label.svelte +16 -0
- package/dist/styles/paper/Label.svelte.d.ts +9 -0
- package/dist/styles/paper/Link.svelte +44 -0
- package/dist/styles/paper/Link.svelte.d.ts +16 -0
- package/dist/styles/paper/LocaleProvider.svelte +24 -0
- package/dist/styles/paper/LocaleProvider.svelte.d.ts +10 -0
- package/dist/styles/paper/LogoCloud.svelte +61 -0
- package/dist/styles/paper/LogoCloud.svelte.d.ts +19 -0
- package/dist/styles/paper/MenuItem.svelte +64 -0
- package/dist/styles/paper/MenuItem.svelte.d.ts +18 -0
- package/dist/styles/paper/MenuSeparator.svelte +22 -0
- package/dist/styles/paper/MenuSeparator.svelte.d.ts +8 -0
- package/dist/styles/paper/Modal.svelte +118 -0
- package/dist/styles/paper/Modal.svelte.d.ts +21 -0
- package/dist/styles/paper/MultiSelect.svelte +287 -0
- package/dist/styles/paper/MultiSelect.svelte.d.ts +31 -0
- package/dist/styles/paper/Navbar.svelte +201 -0
- package/dist/styles/paper/Navbar.svelte.d.ts +33 -0
- package/dist/styles/paper/NumberInput.svelte +96 -0
- package/dist/styles/paper/NumberInput.svelte.d.ts +15 -0
- package/dist/styles/paper/Pagination.svelte +83 -0
- package/dist/styles/paper/Pagination.svelte.d.ts +15 -0
- package/dist/styles/paper/PasswordInput.svelte +100 -0
- package/dist/styles/paper/PasswordInput.svelte.d.ts +12 -0
- package/dist/styles/paper/Popover.svelte +89 -0
- package/dist/styles/paper/Popover.svelte.d.ts +19 -0
- package/dist/styles/paper/PricingCard.svelte +108 -0
- package/dist/styles/paper/PricingCard.svelte.d.ts +25 -0
- package/dist/styles/paper/Progress.svelte +59 -0
- package/dist/styles/paper/Progress.svelte.d.ts +17 -0
- package/dist/styles/paper/Radio.svelte +55 -0
- package/dist/styles/paper/Radio.svelte.d.ts +15 -0
- package/dist/styles/paper/RadioGroup.svelte +95 -0
- package/dist/styles/paper/RadioGroup.svelte.d.ts +27 -0
- package/dist/styles/paper/SegmentedControl.svelte +88 -0
- package/dist/styles/paper/SegmentedControl.svelte.d.ts +25 -0
- package/dist/styles/paper/Select.svelte +61 -0
- package/dist/styles/paper/Select.svelte.d.ts +20 -0
- package/dist/styles/paper/Sidebar.svelte +206 -0
- package/dist/styles/paper/Sidebar.svelte.d.ts +36 -0
- package/dist/styles/paper/Skeleton.svelte +46 -0
- package/dist/styles/paper/Skeleton.svelte.d.ts +13 -0
- package/dist/styles/paper/Slider.svelte +80 -0
- package/dist/styles/paper/Slider.svelte.d.ts +19 -0
- package/dist/styles/paper/Spinner.svelte +34 -0
- package/dist/styles/paper/Spinner.svelte.d.ts +13 -0
- package/dist/styles/paper/Stat.svelte +51 -0
- package/dist/styles/paper/Stat.svelte.d.ts +16 -0
- package/dist/styles/paper/StatsBand.svelte +63 -0
- package/dist/styles/paper/StatsBand.svelte.d.ts +20 -0
- package/dist/styles/paper/Steps.svelte +141 -0
- package/dist/styles/paper/Steps.svelte.d.ts +24 -0
- package/dist/styles/paper/Switch.svelte +64 -0
- package/dist/styles/paper/Switch.svelte.d.ts +14 -0
- package/dist/styles/paper/Table.svelte +217 -0
- package/dist/styles/paper/Table.svelte.d.ts +66 -0
- package/dist/styles/paper/Tabs.svelte +102 -0
- package/dist/styles/paper/Tabs.svelte.d.ts +23 -0
- package/dist/styles/paper/Testimonial.svelte +64 -0
- package/dist/styles/paper/Testimonial.svelte.d.ts +19 -0
- package/dist/styles/paper/Textarea.svelte +54 -0
- package/dist/styles/paper/Textarea.svelte.d.ts +12 -0
- package/dist/styles/paper/ThemeToggle.svelte +130 -0
- package/dist/styles/paper/ThemeToggle.svelte.d.ts +16 -0
- package/dist/styles/paper/Toaster.svelte +132 -0
- package/dist/styles/paper/Toaster.svelte.d.ts +8 -0
- package/dist/styles/paper/Tooltip.svelte +33 -0
- package/dist/styles/paper/Tooltip.svelte.d.ts +12 -0
- package/dist/styles/paper/fonts.css +46 -0
- package/dist/styles/paper/index.d.ts +109 -0
- package/dist/styles/paper/index.js +83 -0
- package/dist/styles/paper/theme.css +222 -0
- package/package.json +531 -201
- package/registry.json +658 -339
- /package/dist/{actions → core/actions}/anchor.d.ts +0 -0
- /package/dist/{actions → core/actions}/anchor.js +0 -0
- /package/dist/{actions → core/actions}/dismissable.d.ts +0 -0
- /package/dist/{actions → core/actions}/dismissable.js +0 -0
- /package/dist/{date.d.ts → core/date.d.ts} +0 -0
- /package/dist/{date.js → core/date.js} +0 -0
- /package/dist/{locale.svelte.d.ts → core/locale.svelte.d.ts} +0 -0
- /package/dist/{locale.svelte.js → core/locale.svelte.js} +0 -0
- /package/dist/{toast.svelte.d.ts → core/toast.svelte.d.ts} +0 -0
- /package/dist/{toast.svelte.js → core/toast.svelte.js} +0 -0
- /package/dist/{tones.d.ts → core/tones.d.ts} +0 -0
- /package/dist/{tones.js → core/tones.js} +0 -0
- /package/dist/{components → styles/matte}/Accordion.svelte +0 -0
- /package/dist/{components → styles/matte}/Accordion.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/AvatarGroup.svelte +0 -0
- /package/dist/{components → styles/matte}/AvatarGroup.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/ContextMenu.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Divider.svelte +0 -0
- /package/dist/{components → styles/matte}/Divider.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Drawer.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Field.svelte +0 -0
- /package/dist/{components → styles/matte}/Field.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/InputGroup.svelte +0 -0
- /package/dist/{components → styles/matte}/InputGroup.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Kbd.svelte +0 -0
- /package/dist/{components → styles/matte}/Kbd.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Label.svelte +0 -0
- /package/dist/{components → styles/matte}/Label.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/MenuSeparator.svelte +0 -0
- /package/dist/{components → styles/matte}/MenuSeparator.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Modal.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Skeleton.svelte +0 -0
- /package/dist/{components → styles/matte}/Skeleton.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Toaster.svelte.d.ts +0 -0
- /package/dist/{components → styles/matte}/Tooltip.svelte +0 -0
- /package/dist/{components → styles/matte}/Tooltip.svelte.d.ts +0 -0
- /package/dist/{fonts.css → styles/matte/fonts.css} +0 -0
- /package/dist/{theme.css → styles/matte/theme.css} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { provideLocale, type Locale } from '../../core/locale.svelte.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Full locale or just the keys you want to change. */
|
|
7
|
+
locale: Partial<Locale>;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { locale, children }: Props = $props();
|
|
12
|
+
|
|
13
|
+
// Seeded during init so the first render already has the right strings —
|
|
14
|
+
// an effect would leave one frame (and the SSR output) in the default
|
|
15
|
+
// language. The effect below only handles later changes, e.g. a switcher.
|
|
16
|
+
// svelte-ignore state_referenced_locally
|
|
17
|
+
const store = provideLocale(locale);
|
|
18
|
+
|
|
19
|
+
$effect(() => {
|
|
20
|
+
store.replace(locale);
|
|
21
|
+
});
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
{@render children()}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type Locale } from '../../core/locale.svelte.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Full locale or just the keys you want to change. */
|
|
5
|
+
locale: Partial<Locale>;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const LocaleProvider: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type LocaleProvider = ReturnType<typeof LocaleProvider>;
|
|
10
|
+
export default LocaleProvider;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
export interface Logo {
|
|
6
|
+
name: string;
|
|
7
|
+
src?: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Without `src` the name is set in the heading face — fine for wordmarks. */
|
|
14
|
+
logos?: Logo[];
|
|
15
|
+
/** Dim until hovered, so the row does not compete with the headline. */
|
|
16
|
+
muted?: boolean;
|
|
17
|
+
/** Your own markup instead of `logos`. */
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
title,
|
|
23
|
+
logos = [],
|
|
24
|
+
muted = true,
|
|
25
|
+
class: className = '',
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<section class="w-full px-6 py-14 {className}" {...rest}>
|
|
32
|
+
<div class="mx-auto flex w-full max-w-6xl flex-col items-center gap-8">
|
|
33
|
+
{#if title}
|
|
34
|
+
<p class="text-center font-sans text-[13px] font-medium text-text-muted">{title}</p>
|
|
35
|
+
{/if}
|
|
36
|
+
|
|
37
|
+
<div class="flex flex-wrap items-center justify-center gap-x-12 gap-y-8">
|
|
38
|
+
{#if children}
|
|
39
|
+
{@render children()}
|
|
40
|
+
{:else}
|
|
41
|
+
{#each logos as logo (logo.name)}
|
|
42
|
+
<svelte:element
|
|
43
|
+
this={logo.href ? 'a' : 'div'}
|
|
44
|
+
href={logo.href}
|
|
45
|
+
class="flex items-center rounded transition-all duration-200 ease-brand-out {muted
|
|
46
|
+
? 'opacity-60 grayscale hover:opacity-100 hover:grayscale-0'
|
|
47
|
+
: ''}"
|
|
48
|
+
>
|
|
49
|
+
{#if logo.src}
|
|
50
|
+
<img src={logo.src} alt={logo.name} class="h-7 w-auto object-contain" />
|
|
51
|
+
{:else}
|
|
52
|
+
<span class="font-heading text-lg font-semibold tracking-tight text-text-secondary">
|
|
53
|
+
{logo.name}
|
|
54
|
+
</span>
|
|
55
|
+
{/if}
|
|
56
|
+
</svelte:element>
|
|
57
|
+
{/each}
|
|
58
|
+
{/if}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
export interface Logo {
|
|
4
|
+
name: string;
|
|
5
|
+
src?: string;
|
|
6
|
+
href?: string;
|
|
7
|
+
}
|
|
8
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Without `src` the name is set in the heading face — fine for wordmarks. */
|
|
11
|
+
logos?: Logo[];
|
|
12
|
+
/** Dim until hovered, so the row does not compete with the headline. */
|
|
13
|
+
muted?: boolean;
|
|
14
|
+
/** Your own markup instead of `logos`. */
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
}
|
|
17
|
+
declare const LogoCloud: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type LogoCloud = ReturnType<typeof LogoCloud>;
|
|
19
|
+
export default LogoCloud;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
import { toneSoftHover, toneText, type Tone } from '../../core/tones.js';
|
|
5
|
+
|
|
6
|
+
interface Props extends HTMLButtonAttributes {
|
|
7
|
+
/** Renders an `<a>` instead of a `<button>`. */
|
|
8
|
+
href?: string;
|
|
9
|
+
tone?: Tone;
|
|
10
|
+
/** Leading glyph. */
|
|
11
|
+
icon?: Snippet;
|
|
12
|
+
/** Right-aligned hint — a keyboard shortcut, a count. */
|
|
13
|
+
shortcut?: string;
|
|
14
|
+
/** Shows a check on the left and marks the item as chosen. */
|
|
15
|
+
selected?: boolean;
|
|
16
|
+
children: Snippet;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
href,
|
|
21
|
+
tone = 'neutral',
|
|
22
|
+
icon,
|
|
23
|
+
shortcut,
|
|
24
|
+
selected = false,
|
|
25
|
+
disabled = false,
|
|
26
|
+
class: className = '',
|
|
27
|
+
children,
|
|
28
|
+
...rest
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<svelte:element
|
|
33
|
+
this={href ? 'a' : 'button'}
|
|
34
|
+
{href}
|
|
35
|
+
type={href ? undefined : 'button'}
|
|
36
|
+
role="menuitem"
|
|
37
|
+
disabled={href ? undefined : disabled}
|
|
38
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
39
|
+
tabindex={disabled ? undefined : -1}
|
|
40
|
+
class="mx-1 flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors duration-100 ease-brand-out focus:outline-none disabled:pointer-events-none disabled:opacity-40
|
|
41
|
+
{tone === 'neutral'
|
|
42
|
+
? 'text-text-secondary hover:bg-bg-inset focus-visible:bg-bg-inset'
|
|
43
|
+
: `${toneText[tone]} ${toneSoftHover[tone]}`}
|
|
44
|
+
{className}"
|
|
45
|
+
{...rest}
|
|
46
|
+
>
|
|
47
|
+
{#if selected}
|
|
48
|
+
<svg class="size-3.5 shrink-0" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
49
|
+
<path
|
|
50
|
+
d="m2 7 3.2 3.2L12 3.6"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
stroke-width="1.8"
|
|
53
|
+
stroke-linecap="round"
|
|
54
|
+
stroke-linejoin="round"
|
|
55
|
+
/>
|
|
56
|
+
</svg>
|
|
57
|
+
{:else if icon}
|
|
58
|
+
<span class="flex size-3.5 shrink-0 items-center justify-center">{@render icon()}</span>
|
|
59
|
+
{/if}
|
|
60
|
+
<span class="min-w-0 flex-1 truncate">{@render children()}</span>
|
|
61
|
+
{#if shortcut}
|
|
62
|
+
<span class="shrink-0 text-[11px] text-text-muted">{shortcut}</span>
|
|
63
|
+
{/if}
|
|
64
|
+
</svelte:element>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Tone } from '../../core/tones.js';
|
|
4
|
+
interface Props extends HTMLButtonAttributes {
|
|
5
|
+
/** Renders an `<a>` instead of a `<button>`. */
|
|
6
|
+
href?: string;
|
|
7
|
+
tone?: Tone;
|
|
8
|
+
/** Leading glyph. */
|
|
9
|
+
icon?: Snippet;
|
|
10
|
+
/** Right-aligned hint — a keyboard shortcut, a count. */
|
|
11
|
+
shortcut?: string;
|
|
12
|
+
/** Shows a check on the left and marks the item as chosen. */
|
|
13
|
+
selected?: boolean;
|
|
14
|
+
children: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const MenuItem: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type MenuItem = ReturnType<typeof MenuItem>;
|
|
18
|
+
export default MenuItem;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/** Optional caption, rendered as a section heading above the rule. */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { label, class: className = '', ...rest }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if label}
|
|
13
|
+
<div
|
|
14
|
+
class="mt-1 px-3 pt-2 pb-1 font-sans text-[11px] font-semibold text-text-muted {className}"
|
|
15
|
+
role="presentation"
|
|
16
|
+
{...rest}
|
|
17
|
+
>
|
|
18
|
+
{label}
|
|
19
|
+
</div>
|
|
20
|
+
{:else}
|
|
21
|
+
<div role="separator" class="my-1 h-px bg-hairline {className}" {...rest}></div>
|
|
22
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Optional caption, rendered as a section heading above the rule. */
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const MenuSeparator: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type MenuSeparator = ReturnType<typeof MenuSeparator>;
|
|
8
|
+
export default MenuSeparator;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
|
|
6
|
+
|
|
7
|
+
export type ModalSize = 'sm' | 'md' | 'lg';
|
|
8
|
+
|
|
9
|
+
interface Props extends HTMLDialogAttributes {
|
|
10
|
+
/** Bindable. Uses the native `<dialog>` top layer + backdrop. */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
size?: ModalSize;
|
|
15
|
+
/** Clicking the backdrop or pressing Escape closes the dialog. */
|
|
16
|
+
dismissible?: boolean;
|
|
17
|
+
/** Show the header's close button. Defaults to `dismissible` — turn it off
|
|
18
|
+
* when the footer already carries the way out. */
|
|
19
|
+
showClose?: boolean;
|
|
20
|
+
footer?: Snippet;
|
|
21
|
+
onclose?: () => void;
|
|
22
|
+
children: Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
open = $bindable(false),
|
|
27
|
+
title,
|
|
28
|
+
description,
|
|
29
|
+
size = 'md',
|
|
30
|
+
dismissible = true,
|
|
31
|
+
showClose,
|
|
32
|
+
footer,
|
|
33
|
+
onclose,
|
|
34
|
+
class: className = '',
|
|
35
|
+
children,
|
|
36
|
+
...rest
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
const t = useLocale();
|
|
40
|
+
let dialog: HTMLDialogElement | null = $state(null);
|
|
41
|
+
|
|
42
|
+
const closable = $derived(showClose ?? dismissible);
|
|
43
|
+
|
|
44
|
+
const sizes: Record<ModalSize, string> = {
|
|
45
|
+
sm: 'max-w-sm',
|
|
46
|
+
md: 'max-w-lg',
|
|
47
|
+
lg: 'max-w-2xl'
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
$effect(() => syncDialog(dialog, open));
|
|
51
|
+
|
|
52
|
+
function close() {
|
|
53
|
+
open = false;
|
|
54
|
+
onclose?.();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function onBackdrop(event: MouseEvent) {
|
|
58
|
+
if (!dismissible) return;
|
|
59
|
+
if (isBackdropClick(event, dialog)) close();
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<dialog
|
|
64
|
+
bind:this={dialog}
|
|
65
|
+
onclose={close}
|
|
66
|
+
onclick={onBackdrop}
|
|
67
|
+
oncancel={(event) => {
|
|
68
|
+
if (!dismissible) event.preventDefault();
|
|
69
|
+
}}
|
|
70
|
+
class="m-auto w-[calc(100%-2rem)] rounded-xl border border-hairline bg-bg p-0 text-text shadow-2xl backdrop:bg-text/30 backdrop:backdrop-blur-[1px] {sizes[
|
|
71
|
+
size
|
|
72
|
+
]} {className}"
|
|
73
|
+
{...rest}
|
|
74
|
+
>
|
|
75
|
+
<div class="flex flex-col">
|
|
76
|
+
{#if title || closable}
|
|
77
|
+
<div class="flex items-start gap-4 px-6 pt-5 pb-3">
|
|
78
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
79
|
+
{#if title}
|
|
80
|
+
<h2 class="font-heading text-base font-semibold text-text">{title}</h2>
|
|
81
|
+
{/if}
|
|
82
|
+
{#if description}
|
|
83
|
+
<p class="font-sans text-[13px] text-text-muted">{description}</p>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{#if closable}
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onclick={close}
|
|
90
|
+
aria-label={t.current.close}
|
|
91
|
+
class="-m-1 shrink-0 rounded-md p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
|
|
92
|
+
>
|
|
93
|
+
<svg class="size-4" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
94
|
+
<path
|
|
95
|
+
d="m2 2 10 10M12 2 2 12"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
stroke-width="1.6"
|
|
98
|
+
stroke-linecap="round"
|
|
99
|
+
/>
|
|
100
|
+
</svg>
|
|
101
|
+
</button>
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
<div class="px-6 py-2 pb-5 font-sans text-sm leading-relaxed text-text-secondary">
|
|
107
|
+
{@render children()}
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{#if footer}
|
|
111
|
+
<div
|
|
112
|
+
class="flex items-center justify-end gap-2 rounded-b-xl border-t border-hairline bg-bg-alt px-6 py-4"
|
|
113
|
+
>
|
|
114
|
+
{@render footer()}
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
</dialog>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLDialogAttributes } from 'svelte/elements';
|
|
3
|
+
export type ModalSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
interface Props extends HTMLDialogAttributes {
|
|
5
|
+
/** Bindable. Uses the native `<dialog>` top layer + backdrop. */
|
|
6
|
+
open?: boolean;
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
size?: ModalSize;
|
|
10
|
+
/** Clicking the backdrop or pressing Escape closes the dialog. */
|
|
11
|
+
dismissible?: boolean;
|
|
12
|
+
/** Show the header's close button. Defaults to `dismissible` — turn it off
|
|
13
|
+
* when the footer already carries the way out. */
|
|
14
|
+
showClose?: boolean;
|
|
15
|
+
footer?: Snippet;
|
|
16
|
+
onclose?: () => void;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const Modal: import("svelte").Component<Props, {}, "open">;
|
|
20
|
+
type Modal = ReturnType<typeof Modal>;
|
|
21
|
+
export default Modal;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface MultiSelectOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
group?: string;
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { anchored } from '../../core/actions/anchor.js';
|
|
13
|
+
import { clickOutside, portal } from '../../core/actions/dismissable.js';
|
|
14
|
+
import { ListCursor, groupItems, matchQuery, revealIndex } from '../../core/list.svelte.js';
|
|
15
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
16
|
+
import { toneFocusWithinBorder, toneSoft, toneText, type Tone } from '../../core/tones.js';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
/** Bindable — the `value` of every chosen option. */
|
|
20
|
+
value?: string[];
|
|
21
|
+
options: MultiSelectOption[];
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
emptyText?: string;
|
|
24
|
+
tone?: Tone;
|
|
25
|
+
invalid?: boolean;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
clearable?: boolean;
|
|
28
|
+
/** Cap on how many may be chosen. `0` means no limit. */
|
|
29
|
+
max?: number;
|
|
30
|
+
/** Collapse to "n selected" past this many chips. `0` shows them all. */
|
|
31
|
+
maxChips?: number;
|
|
32
|
+
id?: string;
|
|
33
|
+
/** Submits one hidden input per value. */
|
|
34
|
+
name?: string;
|
|
35
|
+
onchange?: (value: string[]) => void;
|
|
36
|
+
class?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
value = $bindable([]),
|
|
41
|
+
options,
|
|
42
|
+
placeholder,
|
|
43
|
+
emptyText,
|
|
44
|
+
tone = 'brand',
|
|
45
|
+
invalid = false,
|
|
46
|
+
disabled = false,
|
|
47
|
+
clearable = true,
|
|
48
|
+
max = 0,
|
|
49
|
+
maxChips = 0,
|
|
50
|
+
id,
|
|
51
|
+
name,
|
|
52
|
+
onchange,
|
|
53
|
+
class: className = ''
|
|
54
|
+
}: Props = $props();
|
|
55
|
+
|
|
56
|
+
const t = useLocale();
|
|
57
|
+
|
|
58
|
+
let open = $state(false);
|
|
59
|
+
let query = $state('');
|
|
60
|
+
let wrapper: HTMLElement | null = $state(null);
|
|
61
|
+
let inputEl: HTMLInputElement | null = $state(null);
|
|
62
|
+
let listEl: HTMLElement | null = $state(null);
|
|
63
|
+
|
|
64
|
+
const chosen = $derived(
|
|
65
|
+
value.map((v) => options.find((o) => o.value === v)).filter(Boolean) as MultiSelectOption[]
|
|
66
|
+
);
|
|
67
|
+
const full = $derived(max > 0 && value.length >= max);
|
|
68
|
+
const collapsed = $derived(maxChips > 0 && chosen.length > maxChips);
|
|
69
|
+
|
|
70
|
+
const matches = $derived(
|
|
71
|
+
matchQuery(options, query, (option) => `${option.label} ${option.description ?? ''}`)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const groups = $derived(groupItems(matches));
|
|
75
|
+
const flat = $derived(groups.flatMap(([, items]) => items));
|
|
76
|
+
|
|
77
|
+
const cursor = new ListCursor(() => flat);
|
|
78
|
+
const active = $derived(cursor.index);
|
|
79
|
+
|
|
80
|
+
function toggle(option: MultiSelectOption) {
|
|
81
|
+
const has = value.includes(option.value);
|
|
82
|
+
if (option.disabled || (!has && full)) return;
|
|
83
|
+
value = has ? value.filter((v) => v !== option.value) : [...value, option.value];
|
|
84
|
+
onchange?.(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function remove(option: MultiSelectOption) {
|
|
88
|
+
value = value.filter((v) => v !== option.value);
|
|
89
|
+
onchange?.(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function move(step: number) {
|
|
93
|
+
cursor.move(step);
|
|
94
|
+
revealIndex(listEl, cursor.index);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function onKeydown(event: KeyboardEvent) {
|
|
98
|
+
switch (event.key) {
|
|
99
|
+
case 'ArrowDown':
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
if (!open) open = true;
|
|
102
|
+
else move(1);
|
|
103
|
+
break;
|
|
104
|
+
case 'ArrowUp':
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
if (open) move(-1);
|
|
107
|
+
break;
|
|
108
|
+
case 'Enter':
|
|
109
|
+
if (open && cursor.current) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
toggle(cursor.current);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'Backspace':
|
|
115
|
+
// Empty query: peel the last chip off, the way tag inputs behave.
|
|
116
|
+
if (!query && chosen.length) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
remove(chosen[chosen.length - 1]);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'Escape':
|
|
122
|
+
if (open) {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
event.stopPropagation();
|
|
125
|
+
open = false;
|
|
126
|
+
query = '';
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case 'Tab':
|
|
130
|
+
open = false;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<div
|
|
137
|
+
bind:this={wrapper}
|
|
138
|
+
class="flex w-full flex-wrap items-center gap-1.5 rounded-md border bg-bg py-1.5 pr-1 pl-2 shadow-xs transition-colors duration-150 ease-brand-out
|
|
139
|
+
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
140
|
+
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
141
|
+
>
|
|
142
|
+
{#if collapsed}
|
|
143
|
+
<span
|
|
144
|
+
class="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium {toneSoft[tone]}"
|
|
145
|
+
>
|
|
146
|
+
{chosen.length}
|
|
147
|
+
{t.current.selected}
|
|
148
|
+
</span>
|
|
149
|
+
{:else}
|
|
150
|
+
{#each chosen as option (option.value)}
|
|
151
|
+
<span
|
|
152
|
+
class="inline-flex items-center gap-1 rounded-md py-0.5 pr-0.5 pl-2 text-xs font-medium {toneSoft[
|
|
153
|
+
tone
|
|
154
|
+
]}"
|
|
155
|
+
>
|
|
156
|
+
{option.label}
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onclick={() => remove(option)}
|
|
160
|
+
aria-label="{t.current.removeItem} {option.label}"
|
|
161
|
+
class="inline-flex size-4 items-center justify-center rounded transition-colors duration-150 hover:bg-bg/70 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-current"
|
|
162
|
+
>
|
|
163
|
+
<svg class="size-2.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
164
|
+
<path
|
|
165
|
+
d="m2 2 10 10M12 2 2 12"
|
|
166
|
+
stroke="currentColor"
|
|
167
|
+
stroke-width="2.2"
|
|
168
|
+
stroke-linecap="round"
|
|
169
|
+
/>
|
|
170
|
+
</svg>
|
|
171
|
+
</button>
|
|
172
|
+
</span>
|
|
173
|
+
{/each}
|
|
174
|
+
{/if}
|
|
175
|
+
|
|
176
|
+
<input
|
|
177
|
+
bind:this={inputEl}
|
|
178
|
+
bind:value={query}
|
|
179
|
+
{id}
|
|
180
|
+
{disabled}
|
|
181
|
+
type="text"
|
|
182
|
+
role="combobox"
|
|
183
|
+
autocomplete="off"
|
|
184
|
+
aria-expanded={open}
|
|
185
|
+
aria-controls="{id ?? 'multiselect'}-listbox"
|
|
186
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
187
|
+
placeholder={chosen.length ? '' : (placeholder ?? t.current.comboboxPlaceholder)}
|
|
188
|
+
oninput={() => {
|
|
189
|
+
open = true;
|
|
190
|
+
cursor.index = 0;
|
|
191
|
+
}}
|
|
192
|
+
onfocus={() => (open = true)}
|
|
193
|
+
onkeydown={onKeydown}
|
|
194
|
+
class="h-7 min-w-24 flex-1 bg-transparent px-1 font-sans text-sm text-text placeholder:text-text-muted focus:outline-none"
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
{#if name}
|
|
198
|
+
{#each value as v (v)}<input type="hidden" {name} value={v} />{/each}
|
|
199
|
+
{/if}
|
|
200
|
+
|
|
201
|
+
{#if clearable && chosen.length && !disabled}
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
onclick={() => {
|
|
205
|
+
value = [];
|
|
206
|
+
onchange?.([]);
|
|
207
|
+
inputEl?.focus();
|
|
208
|
+
}}
|
|
209
|
+
aria-label={t.current.clear}
|
|
210
|
+
class="shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
|
|
211
|
+
>
|
|
212
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
213
|
+
<path
|
|
214
|
+
d="m2 2 10 10M12 2 2 12"
|
|
215
|
+
stroke="currentColor"
|
|
216
|
+
stroke-width="1.6"
|
|
217
|
+
stroke-linecap="round"
|
|
218
|
+
/>
|
|
219
|
+
</svg>
|
|
220
|
+
</button>
|
|
221
|
+
{/if}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{#if open}
|
|
225
|
+
<div
|
|
226
|
+
bind:this={listEl}
|
|
227
|
+
use:portal
|
|
228
|
+
use:anchored={{ anchor: wrapper, placement: 'bottom-start', offset: 4, matchWidth: true }}
|
|
229
|
+
use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
|
|
230
|
+
id="{id ?? 'multiselect'}-listbox"
|
|
231
|
+
role="listbox"
|
|
232
|
+
aria-multiselectable="true"
|
|
233
|
+
class="z-50 max-h-64 overflow-y-auto rounded-lg border border-hairline bg-bg py-1 font-sans shadow-lg"
|
|
234
|
+
>
|
|
235
|
+
{#if flat.length === 0}
|
|
236
|
+
<p class="px-3 py-6 text-center text-sm text-text-muted">
|
|
237
|
+
{emptyText ?? t.current.noMatches}
|
|
238
|
+
</p>
|
|
239
|
+
{:else}
|
|
240
|
+
{#each groups as [group, items] (group)}
|
|
241
|
+
{#if group}
|
|
242
|
+
<p class="px-3 pt-2 pb-1 text-[11px] font-semibold text-text-muted">{group}</p>
|
|
243
|
+
{/if}
|
|
244
|
+
{#each items as option (option.value)}
|
|
245
|
+
{@const index = flat.indexOf(option)}
|
|
246
|
+
{@const checked = value.includes(option.value)}
|
|
247
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
248
|
+
<div
|
|
249
|
+
data-index={index}
|
|
250
|
+
role="option"
|
|
251
|
+
tabindex="-1"
|
|
252
|
+
aria-selected={checked}
|
|
253
|
+
aria-disabled={option.disabled || (full && !checked)}
|
|
254
|
+
onclick={() => toggle(option)}
|
|
255
|
+
onpointermove={() => (cursor.index = index)}
|
|
256
|
+
class="mx-1 flex cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-2 text-sm transition-colors duration-100
|
|
257
|
+
{option.disabled || (full && !checked) ? 'pointer-events-none opacity-40' : ''}
|
|
258
|
+
{index === active ? toneSoft[tone] : 'text-text-secondary'}"
|
|
259
|
+
>
|
|
260
|
+
<span
|
|
261
|
+
class="mt-0.5 flex size-4 shrink-0 items-center justify-center rounded border transition-colors duration-100
|
|
262
|
+
{checked ? `border-transparent ${toneText[tone]}` : 'border-hairline-strong'}"
|
|
263
|
+
>
|
|
264
|
+
{#if checked}
|
|
265
|
+
<svg class="size-3" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
266
|
+
<path
|
|
267
|
+
d="m2 6 2.6 2.6L10 3.2"
|
|
268
|
+
stroke="currentColor"
|
|
269
|
+
stroke-width="2"
|
|
270
|
+
stroke-linecap="round"
|
|
271
|
+
stroke-linejoin="round"
|
|
272
|
+
/>
|
|
273
|
+
</svg>
|
|
274
|
+
{/if}
|
|
275
|
+
</span>
|
|
276
|
+
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
277
|
+
<span class="truncate">{option.label}</span>
|
|
278
|
+
{#if option.description}
|
|
279
|
+
<span class="truncate text-xs text-text-muted">{option.description}</span>
|
|
280
|
+
{/if}
|
|
281
|
+
</span>
|
|
282
|
+
</div>
|
|
283
|
+
{/each}
|
|
284
|
+
{/each}
|
|
285
|
+
{/if}
|
|
286
|
+
</div>
|
|
287
|
+
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface MultiSelectOption {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
group?: string;
|
|
7
|
+
}
|
|
8
|
+
import { type Tone } from '../../core/tones.js';
|
|
9
|
+
interface Props {
|
|
10
|
+
/** Bindable — the `value` of every chosen option. */
|
|
11
|
+
value?: string[];
|
|
12
|
+
options: MultiSelectOption[];
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
emptyText?: string;
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
invalid?: boolean;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
clearable?: boolean;
|
|
19
|
+
/** Cap on how many may be chosen. `0` means no limit. */
|
|
20
|
+
max?: number;
|
|
21
|
+
/** Collapse to "n selected" past this many chips. `0` shows them all. */
|
|
22
|
+
maxChips?: number;
|
|
23
|
+
id?: string;
|
|
24
|
+
/** Submits one hidden input per value. */
|
|
25
|
+
name?: string;
|
|
26
|
+
onchange?: (value: string[]) => void;
|
|
27
|
+
class?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const MultiSelect: import("svelte").Component<Props, {}, "value">;
|
|
30
|
+
type MultiSelect = ReturnType<typeof MultiSelect>;
|
|
31
|
+
export default MultiSelect;
|