@nqmcreative/ui 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -34
- package/cli/index.mjs +67 -30
- 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,201 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface NavItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
/** Marks the current page — also sets `aria-current`. */
|
|
6
|
+
active?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
/** Renders a dropdown instead of a plain link. */
|
|
9
|
+
items?: NavItem[];
|
|
10
|
+
/** Small count or tag after the label. */
|
|
11
|
+
badge?: string | number;
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import type { Snippet } from 'svelte';
|
|
17
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
18
|
+
import { focusRing, toneRing, toneSoft, toneText, type Tone } from '../../core/tones.js';
|
|
19
|
+
import Drawer from './Drawer.svelte';
|
|
20
|
+
import Dropdown from './Dropdown.svelte';
|
|
21
|
+
import MenuItem from './MenuItem.svelte';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
items?: NavItem[];
|
|
25
|
+
/** Logo or wordmark on the left. */
|
|
26
|
+
brand?: Snippet;
|
|
27
|
+
/** Buttons on the right — sign in, theme toggle, avatar. */
|
|
28
|
+
actions?: Snippet;
|
|
29
|
+
/** Extra content inside the mobile drawer, under the links. */
|
|
30
|
+
mobileExtra?: Snippet;
|
|
31
|
+
tone?: Tone;
|
|
32
|
+
/** Sticks to the top of the viewport. */
|
|
33
|
+
sticky?: boolean;
|
|
34
|
+
/** Hairline along the bottom edge. Default true. */
|
|
35
|
+
bordered?: boolean;
|
|
36
|
+
/** Bindable — the mobile drawer's state. */
|
|
37
|
+
menuOpen?: boolean;
|
|
38
|
+
class?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
items = [],
|
|
43
|
+
brand,
|
|
44
|
+
actions,
|
|
45
|
+
mobileExtra,
|
|
46
|
+
tone = 'brand',
|
|
47
|
+
sticky = false,
|
|
48
|
+
bordered = true,
|
|
49
|
+
menuOpen = $bindable(false),
|
|
50
|
+
class: className = ''
|
|
51
|
+
}: Props = $props();
|
|
52
|
+
|
|
53
|
+
const t = useLocale();
|
|
54
|
+
|
|
55
|
+
// paper gives the active link a pill rather than a colour change, so the
|
|
56
|
+
// current page is legible without relying on hue alone.
|
|
57
|
+
const link =
|
|
58
|
+
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 font-sans text-sm font-medium transition-colors duration-150 ease-brand-out';
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<header
|
|
62
|
+
class="w-full bg-bg {sticky ? 'sticky top-0 z-40' : ''} {bordered
|
|
63
|
+
? 'border-b border-hairline'
|
|
64
|
+
: ''} {className}"
|
|
65
|
+
>
|
|
66
|
+
<nav
|
|
67
|
+
aria-label={t.current.navigation}
|
|
68
|
+
class="mx-auto flex h-16 w-full max-w-6xl items-center gap-6 px-6"
|
|
69
|
+
>
|
|
70
|
+
{#if brand}
|
|
71
|
+
<div class="flex shrink-0 items-center">{@render brand()}</div>
|
|
72
|
+
{/if}
|
|
73
|
+
|
|
74
|
+
<!-- desktop links -->
|
|
75
|
+
<ul class="hidden flex-1 items-center gap-1 md:flex">
|
|
76
|
+
{#each items as item (item.label)}
|
|
77
|
+
<li>
|
|
78
|
+
{#if item.items?.length}
|
|
79
|
+
<Dropdown label={item.label}>
|
|
80
|
+
{#snippet trigger()}
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class="{link} {focusRing} {toneRing[tone]} {item.active
|
|
84
|
+
? `${toneSoft[tone]}`
|
|
85
|
+
: 'text-text-secondary hover:bg-bg-inset hover:text-text'}"
|
|
86
|
+
>
|
|
87
|
+
{item.label}
|
|
88
|
+
<svg class="size-3" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
89
|
+
<path
|
|
90
|
+
d="m4 6 4 4 4-4"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
stroke-width="1.8"
|
|
93
|
+
stroke-linecap="round"
|
|
94
|
+
/>
|
|
95
|
+
</svg>
|
|
96
|
+
</button>
|
|
97
|
+
{/snippet}
|
|
98
|
+
{#each item.items as child (child.label)}
|
|
99
|
+
<MenuItem href={child.href} disabled={child.disabled} selected={child.active}>
|
|
100
|
+
{child.label}
|
|
101
|
+
</MenuItem>
|
|
102
|
+
{/each}
|
|
103
|
+
</Dropdown>
|
|
104
|
+
{:else}
|
|
105
|
+
<a
|
|
106
|
+
href={item.href}
|
|
107
|
+
aria-current={item.active ? 'page' : undefined}
|
|
108
|
+
class="{link} {focusRing} {toneRing[tone]} {item.active
|
|
109
|
+
? toneSoft[tone]
|
|
110
|
+
: 'text-text-secondary hover:bg-bg-inset hover:text-text'} {item.disabled
|
|
111
|
+
? 'pointer-events-none opacity-50'
|
|
112
|
+
: ''}"
|
|
113
|
+
>
|
|
114
|
+
{item.label}
|
|
115
|
+
{#if item.badge !== undefined}
|
|
116
|
+
<span class="text-[11px] text-text-muted tabular-nums">{item.badge}</span>
|
|
117
|
+
{/if}
|
|
118
|
+
</a>
|
|
119
|
+
{/if}
|
|
120
|
+
</li>
|
|
121
|
+
{/each}
|
|
122
|
+
</ul>
|
|
123
|
+
|
|
124
|
+
<div class="ml-auto flex items-center gap-3">
|
|
125
|
+
{#if actions}
|
|
126
|
+
<div class="hidden items-center gap-2 md:flex">{@render actions()}</div>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
<!-- mobile trigger -->
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
onclick={() => (menuOpen = true)}
|
|
133
|
+
aria-label={t.current.openMenu}
|
|
134
|
+
aria-expanded={menuOpen}
|
|
135
|
+
class="inline-flex size-9 items-center justify-center rounded-md text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text md:hidden {focusRing} {toneRing[
|
|
136
|
+
tone
|
|
137
|
+
]}"
|
|
138
|
+
>
|
|
139
|
+
<svg class="size-5" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
140
|
+
<path
|
|
141
|
+
d="M3 6h14M3 10h14M3 14h14"
|
|
142
|
+
stroke="currentColor"
|
|
143
|
+
stroke-width="1.5"
|
|
144
|
+
stroke-linecap="round"
|
|
145
|
+
/>
|
|
146
|
+
</svg>
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</nav>
|
|
150
|
+
</header>
|
|
151
|
+
|
|
152
|
+
<Drawer bind:open={menuOpen} side="right" size="sm" title={t.current.navigation}>
|
|
153
|
+
<ul class="flex flex-col gap-0.5">
|
|
154
|
+
{#each items as item (item.label)}
|
|
155
|
+
<li>
|
|
156
|
+
<a
|
|
157
|
+
href={item.href}
|
|
158
|
+
onclick={() => (menuOpen = false)}
|
|
159
|
+
aria-current={item.active ? 'page' : undefined}
|
|
160
|
+
class="flex items-center justify-between gap-3 rounded-md px-3 py-2.5 font-sans text-sm font-medium {item.active
|
|
161
|
+
? `${toneSoft[tone]}`
|
|
162
|
+
: 'text-text-secondary hover:bg-bg-inset'} {item.disabled
|
|
163
|
+
? 'pointer-events-none opacity-50'
|
|
164
|
+
: ''}"
|
|
165
|
+
>
|
|
166
|
+
{item.label}
|
|
167
|
+
{#if item.badge !== undefined}
|
|
168
|
+
<span class="text-[11px] text-text-muted tabular-nums">{item.badge}</span>
|
|
169
|
+
{/if}
|
|
170
|
+
</a>
|
|
171
|
+
{#if item.items?.length}
|
|
172
|
+
<ul class="flex flex-col pb-1 pl-3">
|
|
173
|
+
{#each item.items as child (child.label)}
|
|
174
|
+
<li>
|
|
175
|
+
<a
|
|
176
|
+
href={child.href}
|
|
177
|
+
onclick={() => (menuOpen = false)}
|
|
178
|
+
class="block rounded-md px-3 py-2 font-sans text-[13px] text-text-muted hover:bg-bg-inset {toneText[
|
|
179
|
+
tone
|
|
180
|
+
]}"
|
|
181
|
+
>
|
|
182
|
+
{child.label}
|
|
183
|
+
</a>
|
|
184
|
+
</li>
|
|
185
|
+
{/each}
|
|
186
|
+
</ul>
|
|
187
|
+
{/if}
|
|
188
|
+
</li>
|
|
189
|
+
{/each}
|
|
190
|
+
</ul>
|
|
191
|
+
|
|
192
|
+
{#if actions || mobileExtra}
|
|
193
|
+
<div class="mt-6 flex flex-col gap-3 border-t border-hairline pt-6">
|
|
194
|
+
{#if mobileExtra}
|
|
195
|
+
{@render mobileExtra()}
|
|
196
|
+
{:else if actions}
|
|
197
|
+
{@render actions()}
|
|
198
|
+
{/if}
|
|
199
|
+
</div>
|
|
200
|
+
{/if}
|
|
201
|
+
</Drawer>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface NavItem {
|
|
2
|
+
label: string;
|
|
3
|
+
href?: string;
|
|
4
|
+
/** Marks the current page — also sets `aria-current`. */
|
|
5
|
+
active?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Renders a dropdown instead of a plain link. */
|
|
8
|
+
items?: NavItem[];
|
|
9
|
+
/** Small count or tag after the label. */
|
|
10
|
+
badge?: string | number;
|
|
11
|
+
}
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import { type Tone } from '../../core/tones.js';
|
|
14
|
+
interface Props {
|
|
15
|
+
items?: NavItem[];
|
|
16
|
+
/** Logo or wordmark on the left. */
|
|
17
|
+
brand?: Snippet;
|
|
18
|
+
/** Buttons on the right — sign in, theme toggle, avatar. */
|
|
19
|
+
actions?: Snippet;
|
|
20
|
+
/** Extra content inside the mobile drawer, under the links. */
|
|
21
|
+
mobileExtra?: Snippet;
|
|
22
|
+
tone?: Tone;
|
|
23
|
+
/** Sticks to the top of the viewport. */
|
|
24
|
+
sticky?: boolean;
|
|
25
|
+
/** Hairline along the bottom edge. Default true. */
|
|
26
|
+
bordered?: boolean;
|
|
27
|
+
/** Bindable — the mobile drawer's state. */
|
|
28
|
+
menuOpen?: boolean;
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const Navbar: import("svelte").Component<Props, {}, "menuOpen">;
|
|
32
|
+
type Navbar = ReturnType<typeof Navbar>;
|
|
33
|
+
export default Navbar;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { clampToStep } from '../../core/number.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
8
|
+
value?: number;
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
step?: number;
|
|
12
|
+
tone?: Tone;
|
|
13
|
+
invalid?: boolean;
|
|
14
|
+
/** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
|
|
15
|
+
unit?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
value = $bindable(0),
|
|
20
|
+
min = -Infinity,
|
|
21
|
+
max = Infinity,
|
|
22
|
+
step = 1,
|
|
23
|
+
tone = 'brand',
|
|
24
|
+
invalid = false,
|
|
25
|
+
unit,
|
|
26
|
+
disabled = false,
|
|
27
|
+
class: className = '',
|
|
28
|
+
...rest
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
const t = useLocale();
|
|
32
|
+
|
|
33
|
+
const clamp = (next: number) => clampToStep(next, min, max, step);
|
|
34
|
+
|
|
35
|
+
function nudge(direction: 1 | -1) {
|
|
36
|
+
value = clamp(value + direction * step);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function onKeydown(event: KeyboardEvent) {
|
|
40
|
+
if (event.key === 'ArrowUp') {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
nudge(1);
|
|
43
|
+
} else if (event.key === 'ArrowDown') {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
nudge(-1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const stepper =
|
|
50
|
+
'flex size-7 shrink-0 items-center justify-center rounded text-text-secondary transition-colors duration-150 hover:bg-bg-inset hover:text-text disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current';
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<span
|
|
54
|
+
class="inline-flex w-full items-center rounded-md border bg-bg pr-1 pl-3 shadow-xs transition-colors duration-150 ease-brand-out
|
|
55
|
+
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
56
|
+
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
57
|
+
>
|
|
58
|
+
<input
|
|
59
|
+
type="number"
|
|
60
|
+
{min}
|
|
61
|
+
{max}
|
|
62
|
+
{step}
|
|
63
|
+
{disabled}
|
|
64
|
+
{value}
|
|
65
|
+
oninput={(event) => (value = clamp(event.currentTarget.valueAsNumber))}
|
|
66
|
+
onkeydown={onKeydown}
|
|
67
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
68
|
+
class="h-10 w-full min-w-0 [appearance:textfield] bg-transparent font-sans text-sm text-text tabular-nums placeholder:text-text-muted focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
69
|
+
{...rest}
|
|
70
|
+
/>
|
|
71
|
+
{#if unit}
|
|
72
|
+
<span class="shrink-0 px-1.5 text-xs text-text-muted">{unit}</span>
|
|
73
|
+
{/if}
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
class={stepper}
|
|
77
|
+
onclick={() => nudge(-1)}
|
|
78
|
+
disabled={disabled || value <= min}
|
|
79
|
+
aria-label={t.current.decrease}
|
|
80
|
+
>
|
|
81
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
82
|
+
<path d="M2.5 7h9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
class={stepper}
|
|
88
|
+
onclick={() => nudge(1)}
|
|
89
|
+
disabled={disabled || value >= max}
|
|
90
|
+
aria-label={t.current.increase}
|
|
91
|
+
>
|
|
92
|
+
<svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
93
|
+
<path d="M7 2.5v9M2.5 7h9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
94
|
+
</svg>
|
|
95
|
+
</button>
|
|
96
|
+
</span>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type' | 'value' | 'min' | 'max'> {
|
|
4
|
+
value?: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
/** Short unit rendered inside the field, e.g. `'px'` or `'%'`. */
|
|
11
|
+
unit?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const NumberInput: import("svelte").Component<Props, {}, "value">;
|
|
14
|
+
type NumberInput = ReturnType<typeof NumberInput>;
|
|
15
|
+
export default NumberInput;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { focusRing, toneRing, toneSolid, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { pageRange } from '../../core/pagination.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
8
|
+
/** Bindable, 1-based. */
|
|
9
|
+
page?: number;
|
|
10
|
+
/** Total number of pages. */
|
|
11
|
+
total: number;
|
|
12
|
+
/** Pages shown either side of the current one before collapsing to `…`. */
|
|
13
|
+
siblings?: number;
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
onchange?: (page: number) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
page = $bindable(1),
|
|
20
|
+
total,
|
|
21
|
+
siblings = 1,
|
|
22
|
+
tone = 'brand',
|
|
23
|
+
class: className = '',
|
|
24
|
+
onchange,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const t = useLocale();
|
|
29
|
+
|
|
30
|
+
const pages = $derived(pageRange(page, total, siblings));
|
|
31
|
+
|
|
32
|
+
function go(next: number) {
|
|
33
|
+
const clamped = Math.min(total, Math.max(1, next));
|
|
34
|
+
if (clamped === page) return;
|
|
35
|
+
page = clamped;
|
|
36
|
+
onchange?.(clamped);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const cell = `inline-flex h-9 min-w-9 items-center justify-center rounded-md px-2 font-sans text-sm font-medium tabular-nums transition-colors duration-150 ease-brand-out disabled:opacity-40 disabled:pointer-events-none ${focusRing}`;
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<nav aria-label={t.current.pagination} class="flex items-center gap-1 {className}" {...rest}>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
|
|
46
|
+
disabled={page <= 1}
|
|
47
|
+
aria-label={t.current.previousPage}
|
|
48
|
+
onclick={() => go(page - 1)}
|
|
49
|
+
>
|
|
50
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
51
|
+
<path d="m10 4-4 4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
{#each pages as item, i (`${item}-${i}`)}
|
|
56
|
+
{#if item === '…'}
|
|
57
|
+
<span class="{cell} text-text-muted">…</span>
|
|
58
|
+
{:else}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
aria-current={item === page ? 'page' : undefined}
|
|
62
|
+
class="{cell} {toneRing[tone]} {item === page
|
|
63
|
+
? `${toneSolid[tone]} shadow-xs`
|
|
64
|
+
: 'text-text-secondary hover:bg-bg-inset'}"
|
|
65
|
+
onclick={() => go(item)}
|
|
66
|
+
>
|
|
67
|
+
{item}
|
|
68
|
+
</button>
|
|
69
|
+
{/if}
|
|
70
|
+
{/each}
|
|
71
|
+
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
class="{cell} {toneRing[tone]} text-text-secondary hover:bg-bg-inset"
|
|
75
|
+
disabled={page >= total}
|
|
76
|
+
aria-label={t.current.nextPage}
|
|
77
|
+
onclick={() => go(page + 1)}
|
|
78
|
+
>
|
|
79
|
+
<svg class="size-4" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
80
|
+
<path d="m6 4 4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" />
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
</nav>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'onchange'> {
|
|
4
|
+
/** Bindable, 1-based. */
|
|
5
|
+
page?: number;
|
|
6
|
+
/** Total number of pages. */
|
|
7
|
+
total: number;
|
|
8
|
+
/** Pages shown either side of the current one before collapsing to `…`. */
|
|
9
|
+
siblings?: number;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
onchange?: (page: number) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const Pagination: import("svelte").Component<Props, {}, "page">;
|
|
14
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
15
|
+
export default Pagination;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
|
|
4
|
+
import { useLocale } from '../../core/locale.svelte.js';
|
|
5
|
+
import { passwordScore } from '../../core/password.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type'> {
|
|
8
|
+
value?: string;
|
|
9
|
+
tone?: Tone;
|
|
10
|
+
invalid?: boolean;
|
|
11
|
+
/** Draws a four-segment strength meter under the field. */
|
|
12
|
+
strength?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
value = $bindable(''),
|
|
17
|
+
tone = 'brand',
|
|
18
|
+
invalid = false,
|
|
19
|
+
strength = false,
|
|
20
|
+
disabled = false,
|
|
21
|
+
class: className = '',
|
|
22
|
+
...rest
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
const t = useLocale();
|
|
26
|
+
|
|
27
|
+
let visible = $state(false);
|
|
28
|
+
|
|
29
|
+
const score = $derived(passwordScore(value));
|
|
30
|
+
|
|
31
|
+
const bars = ['bg-danger', 'bg-danger', 'bg-warning', 'bg-info', 'bg-success'];
|
|
32
|
+
const texts = ['text-danger', 'text-danger', 'text-warning', 'text-info', 'text-success'];
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="flex w-full flex-col gap-2">
|
|
36
|
+
<span
|
|
37
|
+
class="inline-flex w-full items-center gap-2 rounded-md border bg-bg pr-1 pl-3 shadow-xs transition-colors duration-150 ease-brand-out
|
|
38
|
+
{invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
|
|
39
|
+
{disabled ? 'pointer-events-none opacity-50' : ''} {className}"
|
|
40
|
+
>
|
|
41
|
+
<input
|
|
42
|
+
bind:value
|
|
43
|
+
{disabled}
|
|
44
|
+
type={visible ? 'text' : 'password'}
|
|
45
|
+
aria-invalid={invalid ? 'true' : undefined}
|
|
46
|
+
class="h-10 w-full min-w-0 bg-transparent font-sans text-sm text-text placeholder:text-text-muted focus:outline-none"
|
|
47
|
+
{...rest}
|
|
48
|
+
/>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onclick={() => (visible = !visible)}
|
|
52
|
+
aria-label={visible ? t.current.hidePassword : t.current.showPassword}
|
|
53
|
+
aria-pressed={visible}
|
|
54
|
+
class="shrink-0 rounded p-1.5 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"
|
|
55
|
+
>
|
|
56
|
+
{#if visible}
|
|
57
|
+
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
58
|
+
<path
|
|
59
|
+
d="M2.5 10S5.5 4.5 10 4.5 17.5 10 17.5 10 14.5 15.5 10 15.5 2.5 10 2.5 10Z"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
stroke-width="1.4"
|
|
62
|
+
/>
|
|
63
|
+
<circle cx="10" cy="10" r="2.4" stroke="currentColor" stroke-width="1.4" />
|
|
64
|
+
<path
|
|
65
|
+
d="m3.5 16.5 13-13"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
stroke-width="1.4"
|
|
68
|
+
stroke-linecap="round"
|
|
69
|
+
/>
|
|
70
|
+
</svg>
|
|
71
|
+
{:else}
|
|
72
|
+
<svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
73
|
+
<path
|
|
74
|
+
d="M2.5 10S5.5 4.5 10 4.5 17.5 10 17.5 10 14.5 15.5 10 15.5 2.5 10 2.5 10Z"
|
|
75
|
+
stroke="currentColor"
|
|
76
|
+
stroke-width="1.4"
|
|
77
|
+
/>
|
|
78
|
+
<circle cx="10" cy="10" r="2.4" stroke="currentColor" stroke-width="1.4" />
|
|
79
|
+
</svg>
|
|
80
|
+
{/if}
|
|
81
|
+
</button>
|
|
82
|
+
</span>
|
|
83
|
+
|
|
84
|
+
{#if strength}
|
|
85
|
+
<div class="flex items-center gap-3">
|
|
86
|
+
<div class="flex flex-1 gap-1" aria-hidden="true">
|
|
87
|
+
{#each [0, 1, 2, 3] as segment (segment)}
|
|
88
|
+
<span
|
|
89
|
+
class="h-1 flex-1 rounded-full transition-colors duration-200 {segment < score
|
|
90
|
+
? bars[score]
|
|
91
|
+
: 'bg-bg-inset'}"
|
|
92
|
+
></span>
|
|
93
|
+
{/each}
|
|
94
|
+
</div>
|
|
95
|
+
<span class="text-xs font-medium {value ? texts[score] : 'text-text-muted'}">
|
|
96
|
+
{value ? t.current.passwordStrength[score] : '—'}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
{/if}
|
|
100
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import { type Tone } from '../../core/tones.js';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size' | 'type'> {
|
|
4
|
+
value?: string;
|
|
5
|
+
tone?: Tone;
|
|
6
|
+
invalid?: boolean;
|
|
7
|
+
/** Draws a four-segment strength meter under the field. */
|
|
8
|
+
strength?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const PasswordInput: import("svelte").Component<Props, {}, "value">;
|
|
11
|
+
type PasswordInput = ReturnType<typeof PasswordInput>;
|
|
12
|
+
export default PasswordInput;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { anchored, type Placement } from '../../core/actions/anchor.js';
|
|
4
|
+
import { clickOutside, focusTrap, portal } from '../../core/actions/dismissable.js';
|
|
5
|
+
import { HOVER_GRACE, describeTrigger, popupTrigger } from '../../core/trigger.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
trigger: Snippet;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
open?: boolean;
|
|
11
|
+
placement?: Placement;
|
|
12
|
+
offset?: number;
|
|
13
|
+
title?: string;
|
|
14
|
+
/** `'hover'` also opens on focus, for read-only content. */
|
|
15
|
+
on?: 'click' | 'hover';
|
|
16
|
+
/** Keep focus where it is — right for hover popovers and hover cards. */
|
|
17
|
+
trapFocus?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
triggerClass?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
trigger,
|
|
24
|
+
children,
|
|
25
|
+
open = $bindable(false),
|
|
26
|
+
placement = 'bottom',
|
|
27
|
+
offset = 8,
|
|
28
|
+
title,
|
|
29
|
+
on = 'click',
|
|
30
|
+
trapFocus = true,
|
|
31
|
+
class: className = '',
|
|
32
|
+
triggerClass = ''
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
let anchorEl: HTMLElement | null = $state(null);
|
|
36
|
+
/** Held so the panel itself can cancel the hover close it inherited. */
|
|
37
|
+
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
|
|
39
|
+
$effect(() => describeTrigger(anchorEl, 'dialog', open));
|
|
40
|
+
|
|
41
|
+
function onKeydown(event: KeyboardEvent) {
|
|
42
|
+
if (event.key !== 'Escape') return;
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
open = false;
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<span
|
|
50
|
+
bind:this={anchorEl}
|
|
51
|
+
use:popupTrigger={{
|
|
52
|
+
toggle: () => (open = !open),
|
|
53
|
+
open: () => (open = true),
|
|
54
|
+
close: () => (open = false),
|
|
55
|
+
on
|
|
56
|
+
}}
|
|
57
|
+
class="inline-flex {triggerClass}"
|
|
58
|
+
>
|
|
59
|
+
{@render trigger()}
|
|
60
|
+
</span>
|
|
61
|
+
|
|
62
|
+
{#if open}
|
|
63
|
+
<div
|
|
64
|
+
use:portal
|
|
65
|
+
use:anchored={{ anchor: anchorEl, placement, offset }}
|
|
66
|
+
use:clickOutside={{
|
|
67
|
+
onoutside: () => (open = false),
|
|
68
|
+
ignore: [anchorEl],
|
|
69
|
+
enabled: on === 'click'
|
|
70
|
+
}}
|
|
71
|
+
use:focusTrap={{ enabled: trapFocus, autofocus: trapFocus, restore: trapFocus }}
|
|
72
|
+
onkeydown={onKeydown}
|
|
73
|
+
onpointerenter={() => clearTimeout(closeTimer)}
|
|
74
|
+
onpointerleave={() => {
|
|
75
|
+
if (on === 'hover') closeTimer = setTimeout(() => (open = false), HOVER_GRACE);
|
|
76
|
+
}}
|
|
77
|
+
role="dialog"
|
|
78
|
+
aria-label={title}
|
|
79
|
+
tabindex="-1"
|
|
80
|
+
class="z-50 flex max-w-xs flex-col gap-2 rounded-lg border border-hairline bg-bg p-4 font-sans shadow-lg {className}"
|
|
81
|
+
>
|
|
82
|
+
{#if title}
|
|
83
|
+
<p class="font-heading text-sm font-semibold text-text">{title}</p>
|
|
84
|
+
{/if}
|
|
85
|
+
<div class="text-[13px] leading-relaxed text-text-secondary">
|
|
86
|
+
{@render children()}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type Placement } from '../../core/actions/anchor.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
trigger: Snippet;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
placement?: Placement;
|
|
8
|
+
offset?: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
/** `'hover'` also opens on focus, for read-only content. */
|
|
11
|
+
on?: 'click' | 'hover';
|
|
12
|
+
/** Keep focus where it is — right for hover popovers and hover cards. */
|
|
13
|
+
trapFocus?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
triggerClass?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const Popover: import("svelte").Component<Props, {}, "open">;
|
|
18
|
+
type Popover = ReturnType<typeof Popover>;
|
|
19
|
+
export default Popover;
|