@widgetic/design-system 0.5.23 → 0.5.25
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/dist/components/CopyableHeader.svelte +5 -3
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/modals/BaseModal.svelte +257 -166
- package/dist/components/modals/BaseModal.svelte.d.ts +3 -0
- package/dist/components/modals/NewItemDialog.svelte +5 -7
- package/dist/components/modals/Portal.svelte +9 -7
- package/dist/components/modals/RenameDialog.svelte +5 -7
- package/dist/components/modals/modalStore.d.ts +9 -1
- package/dist/components/modals/modalStore.js +44 -11
- package/dist/components/navigation/Breadcrumbs.svelte +10 -4
- package/dist/components/navigation/breadcrumbsAvailableWidth.d.ts +10 -0
- package/dist/components/navigation/breadcrumbsAvailableWidth.js +10 -0
- package/dist/components/search-input/SearchInput.svelte +25 -13
- package/dist/components/search-input/SearchInput.svelte.d.ts +4 -0
- package/dist/components/search-input/index2.d.ts +14 -9
- package/dist/components/shell/NavDrawer.svelte +1 -1
- package/dist/components/shell/NavRow.svelte +4 -1
- package/dist/components/shell/SettingsDialog.svelte +48 -29
- package/dist/components/shell/SettingsDialog.svelte.d.ts +0 -4
- package/dist/components/shell/WidgetRow.svelte.d.ts +1 -1
- package/dist/components/shell/index.d.ts +0 -1
- package/dist/components/shell/index.js +0 -1
- package/dist/components/shell/nested-modal-guard.d.ts +25 -0
- package/dist/components/shell/nested-modal-guard.js +33 -0
- package/dist/components/ui/alert/index.d.ts +2 -2
- package/dist/components/ui/alert/index.js +4 -1
- package/dist/components/ui/avatar/avatar.svelte +22 -3
- package/dist/components/ui/button/button.svelte +1 -1
- package/dist/components/ui/button/button.svelte.d.ts +2 -2
- package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-item.svelte +1 -1
- package/dist/components/ui/command/command-link-item.svelte +1 -1
- package/dist/components/ui/dialog/dialog-content.svelte +8 -1
- package/dist/components/ui/dialog/dialog-overlay.svelte +8 -1
- package/dist/components/ui/drawer/drawer-overlay.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/dist/components/ui/icon-button/icon-button.svelte +1 -1
- package/dist/components/ui/icon-button/icon-button.svelte.d.ts +2 -2
- package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
- package/dist/components/ui/select/select-content.svelte +5 -0
- package/dist/components/ui/select/select-item.svelte +8 -1
- package/dist/components/ui/select/select-trigger.svelte +3 -2
- package/dist/components/ui/stepper-button/stepper-button.svelte +1 -1
- package/dist/components/ui/switch/switch.svelte +4 -1
- package/dist/styles/global.css +3 -2
- package/dist/styles/tokens.css +21 -1
- package/dist/utils/inputModality.d.ts +16 -0
- package/dist/utils/inputModality.js +55 -0
- package/dist/utils/overlayMotion.d.ts +49 -0
- package/dist/utils/overlayMotion.js +71 -0
- package/package.json +1 -1
- package/dist/components/shell/messages.d.ts +0 -7
- package/dist/components/shell/messages.js +0 -5
|
@@ -50,14 +50,33 @@
|
|
|
50
50
|
} = $props();
|
|
51
51
|
|
|
52
52
|
const initials = $derived(avatarInitials(name || alt));
|
|
53
|
+
|
|
54
|
+
// The picture: the initials (or the icon) are always drawn first, and the picture fades in on top of them once it has
|
|
55
|
+
// loaded. A picture that fails (a Gravatar asked with `d=404` answers 404 for everybody who has none, a blocked or an
|
|
56
|
+
// offline request) never shows the browser's broken-image glyph: the initials simply stay. Both states belong to the
|
|
57
|
+
// address that produced them, so a new `src` starts from scratch.
|
|
58
|
+
let loadedSource = $state<string | undefined>(undefined);
|
|
59
|
+
let failedSource = $state<string | undefined>(undefined);
|
|
60
|
+
const showPicture = $derived(Boolean(src) && src !== failedSource);
|
|
61
|
+
const pictureLoaded = $derived(Boolean(src) && src === loadedSource);
|
|
53
62
|
</script>
|
|
54
63
|
|
|
55
64
|
<span class={cn(avatarVariants({ size, type }), className)} {...restProps}>
|
|
56
|
-
{#if
|
|
57
|
-
<img {src} {alt} class="size-full object-cover" />
|
|
58
|
-
{:else if type === "icon" && icon}
|
|
65
|
+
{#if type === "icon" && icon}
|
|
59
66
|
<span class="flex items-center justify-center">{@render icon()}</span>
|
|
60
67
|
{:else}
|
|
61
68
|
<span aria-hidden="true">{initials}</span>
|
|
62
69
|
{/if}
|
|
70
|
+
{#if showPicture}
|
|
71
|
+
<img
|
|
72
|
+
{src}
|
|
73
|
+
{alt}
|
|
74
|
+
class={cn(
|
|
75
|
+
"absolute inset-0 size-full object-cover transition-opacity duration-base ease-out",
|
|
76
|
+
pictureLoaded ? "opacity-100" : "opacity-0",
|
|
77
|
+
)}
|
|
78
|
+
onload={() => (loadedSource = src)}
|
|
79
|
+
onerror={() => (failedSource = src)}
|
|
80
|
+
/>
|
|
81
|
+
{/if}
|
|
63
82
|
</span>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { VariantProps } from "tailwind-variants";
|
|
14
14
|
|
|
15
15
|
export const buttonVariants = tv({
|
|
16
|
-
base: "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label
|
|
16
|
+
base: "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]",
|
|
17
17
|
variants: {
|
|
18
18
|
variant: {
|
|
19
19
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
@@ -23,7 +23,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
|
|
|
23
23
|
true: string;
|
|
24
24
|
false: string;
|
|
25
25
|
};
|
|
26
|
-
}, undefined, "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label
|
|
26
|
+
}, undefined, "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]", {
|
|
27
27
|
responsiveVariants?: boolean | (string | number | symbol)[] | {
|
|
28
28
|
size?: boolean | (string | number | symbol)[] | undefined;
|
|
29
29
|
variant?: boolean | (string | number | symbol)[] | undefined;
|
|
@@ -81,7 +81,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
|
|
|
81
81
|
true: string;
|
|
82
82
|
false: string;
|
|
83
83
|
};
|
|
84
|
-
}, undefined, "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label
|
|
84
|
+
}, undefined, "inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg text-label press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]", import("tailwind-variants/dist/config.js").TVConfig<{
|
|
85
85
|
variant: {
|
|
86
86
|
default: string;
|
|
87
87
|
destructive: string;
|
|
@@ -6,6 +6,6 @@ export type CalendarProps = WithoutChildrenOrChild<CalendarPrimitive.RootProps>
|
|
|
6
6
|
nextLabel?: string;
|
|
7
7
|
};
|
|
8
8
|
import * as Calendar from "./index.js";
|
|
9
|
-
declare const Calendar: import("svelte").Component<CalendarProps, {}, "
|
|
9
|
+
declare const Calendar: import("svelte").Component<CalendarProps, {}, "ref" | "value" | "placeholder">;
|
|
10
10
|
type Calendar = ReturnType<typeof Calendar>;
|
|
11
11
|
export default Calendar;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<CommandPrimitive.Item
|
|
17
17
|
class={cn(
|
|
18
|
-
"text-body aria-selected:bg-accent aria-selected:text-foreground relative flex h-8 cursor-
|
|
18
|
+
"text-body aria-selected:bg-accent aria-selected:text-foreground relative flex h-8 cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 outline-hidden transition-colors duration-fast data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]",
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
bind:ref
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<CommandPrimitive.LinkItem
|
|
17
17
|
class={cn(
|
|
18
|
-
"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-
|
|
18
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
bind:ref
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
import type { Snippet } from "svelte";
|
|
5
5
|
import * as Dialog from "./index.js";
|
|
6
6
|
import { cn } from "../../../utils.js";
|
|
7
|
+
import {
|
|
8
|
+
overlayDialogContentClasses,
|
|
9
|
+
overlayReducedMotionClasses
|
|
10
|
+
} from "../../../utils/overlayMotion.js";
|
|
7
11
|
|
|
8
12
|
let {
|
|
9
13
|
ref = $bindable(null),
|
|
@@ -24,8 +28,11 @@
|
|
|
24
28
|
<Dialog.Overlay />
|
|
25
29
|
<DialogPrimitive.Content
|
|
26
30
|
bind:ref
|
|
31
|
+
data-widgetic-overlay="dialog-panel"
|
|
27
32
|
class={cn(
|
|
28
|
-
"
|
|
33
|
+
"bg-background border-border fixed start-1/2 top-1/2 z-50 grid w-dialog max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-panel border p-6",
|
|
34
|
+
overlayDialogContentClasses,
|
|
35
|
+
overlayReducedMotionClasses,
|
|
29
36
|
className
|
|
30
37
|
)}
|
|
31
38
|
{...restProps}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
<script lang="ts">
|
|
7
7
|
import { cn } from "../../../utils.js";
|
|
8
|
+
import {
|
|
9
|
+
overlayDialogOverlayClasses,
|
|
10
|
+
overlayReducedMotionClasses
|
|
11
|
+
} from "../../../utils/overlayMotion.js";
|
|
8
12
|
|
|
9
13
|
let {
|
|
10
14
|
ref = $bindable(null),
|
|
@@ -15,8 +19,11 @@
|
|
|
15
19
|
|
|
16
20
|
<DialogPrimitive.Overlay
|
|
17
21
|
bind:ref
|
|
22
|
+
data-widgetic-overlay="dialog-backdrop"
|
|
18
23
|
class={cn(
|
|
19
|
-
"
|
|
24
|
+
"fixed inset-0 z-50",
|
|
25
|
+
overlayDialogOverlayClasses,
|
|
26
|
+
overlayReducedMotionClasses,
|
|
20
27
|
className
|
|
21
28
|
)}
|
|
22
29
|
{...restProps}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
bind:checked
|
|
23
23
|
bind:indeterminate
|
|
24
24
|
class={cn(
|
|
25
|
-
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex h-8 cursor-
|
|
25
|
+
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex h-8 cursor-pointer select-none items-center rounded-lg py-1.5 ps-8 pe-2 outline-hidden transition-colors duration-fast data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
26
26
|
className
|
|
27
27
|
)}
|
|
28
28
|
{...restProps}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<DropdownMenuPrimitive.Item
|
|
23
23
|
bind:ref
|
|
24
24
|
class={cn(
|
|
25
|
-
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex cursor-
|
|
25
|
+
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 outline-hidden transition-colors duration-fast data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333] data-[variant=destructive]:text-destructive data-highlighted:data-[variant=destructive]:text-destructive",
|
|
26
26
|
description ? "min-h-12" : "min-h-8",
|
|
27
27
|
inset && "ps-8",
|
|
28
28
|
className
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<DropdownMenuPrimitive.RadioItem
|
|
19
19
|
bind:ref
|
|
20
20
|
class={cn(
|
|
21
|
-
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex h-8 cursor-
|
|
21
|
+
"text-body data-highlighted:bg-accent data-highlighted:text-foreground relative flex h-8 cursor-pointer select-none items-center rounded-lg py-1.5 ps-8 pe-2 outline-hidden transition-colors duration-fast data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
22
22
|
className
|
|
23
23
|
)}
|
|
24
24
|
{...restProps}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<DropdownMenuPrimitive.SubTrigger
|
|
18
18
|
bind:ref
|
|
19
19
|
class={cn(
|
|
20
|
-
"text-body data-highlighted:bg-accent data-[state=open]:bg-accent flex h-8 cursor-
|
|
20
|
+
"text-body data-highlighted:bg-accent data-[state=open]:bg-accent flex h-8 cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 outline-hidden transition-colors duration-fast [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]",
|
|
21
21
|
inset && "ps-8",
|
|
22
22
|
className
|
|
23
23
|
)}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { VariantProps } from "tailwind-variants";
|
|
9
9
|
|
|
10
10
|
export const iconButtonVariants = tv({
|
|
11
|
-
base: "inline-flex shrink-0 items-center justify-center
|
|
11
|
+
base: "inline-flex shrink-0 items-center justify-center press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]",
|
|
12
12
|
variants: {
|
|
13
13
|
variant: {
|
|
14
14
|
ghost:
|
|
@@ -13,7 +13,7 @@ export declare const iconButtonVariants: import("tailwind-variants").TVReturnTyp
|
|
|
13
13
|
default: string;
|
|
14
14
|
sm: string;
|
|
15
15
|
};
|
|
16
|
-
}, undefined, "inline-flex shrink-0 items-center justify-center
|
|
16
|
+
}, undefined, "inline-flex shrink-0 items-center justify-center press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]", {
|
|
17
17
|
responsiveVariants?: boolean | (string | number | symbol)[] | {
|
|
18
18
|
size?: boolean | (string | number | symbol)[] | undefined;
|
|
19
19
|
variant?: boolean | (string | number | symbol)[] | undefined;
|
|
@@ -50,7 +50,7 @@ export declare const iconButtonVariants: import("tailwind-variants").TVReturnTyp
|
|
|
50
50
|
default: string;
|
|
51
51
|
sm: string;
|
|
52
52
|
};
|
|
53
|
-
}, undefined, "inline-flex shrink-0 items-center justify-center
|
|
53
|
+
}, undefined, "inline-flex shrink-0 items-center justify-center press-feedback focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:stroke-[1.333]", import("tailwind-variants/dist/config.js").TVConfig<{
|
|
54
54
|
variant: {
|
|
55
55
|
ghost: string;
|
|
56
56
|
secondary: string;
|
|
@@ -6,6 +6,6 @@ export type RangeCalendarProps = WithoutChildrenOrChild<RangeCalendarPrimitive.R
|
|
|
6
6
|
nextLabel?: string;
|
|
7
7
|
};
|
|
8
8
|
import * as RangeCalendar from "./index.js";
|
|
9
|
-
declare const RangeCalendar: import("svelte").Component<RangeCalendarProps, {}, "
|
|
9
|
+
declare const RangeCalendar: import("svelte").Component<RangeCalendarProps, {}, "ref" | "value" | "placeholder">;
|
|
10
10
|
type RangeCalendar = ReturnType<typeof RangeCalendar>;
|
|
11
11
|
export default RangeCalendar;
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
|
|
3
3
|
import SelectScrollUpButton from "./select-scroll-up-button.svelte";
|
|
4
4
|
import SelectScrollDownButton from "./select-scroll-down-button.svelte";
|
|
5
|
+
import { onMount } from "svelte";
|
|
5
6
|
import { cn } from "../../../utils.js";
|
|
6
7
|
import { useTextDirection } from "../../textDirection.js";
|
|
8
|
+
import { installInputModality } from "../../../utils/inputModality.js";
|
|
7
9
|
|
|
8
10
|
const textDirection = useTextDirection();
|
|
9
11
|
|
|
12
|
+
// The rows paint the highlight only while the keyboard drives the list (see select-item.svelte): this is what tells them so
|
|
13
|
+
onMount(() => installInputModality());
|
|
14
|
+
|
|
10
15
|
let {
|
|
11
16
|
ref = $bindable(null),
|
|
12
17
|
class: className,
|
|
@@ -18,11 +18,18 @@
|
|
|
18
18
|
}: SelectItemProps = $props();
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
|
+
<!--
|
|
22
|
+
Which row is painted. bits-ui highlights the row the pointer moves over and never takes the highlight back when the pointer
|
|
23
|
+
leaves, so painting `data-highlighted` left the last row crossed lit after the pointer had gone. So:
|
|
24
|
+
- the pointer paints with :hover, which ends when the pointer does (a selected row keeps its own look);
|
|
25
|
+
- the keyboard paints `data-highlighted`, while the last input was a key press (data-input-modality="keyboard", set by
|
|
26
|
+
utils/inputModality.ts), and the hover of a pointer that is resting somewhere does not paint a second row then.
|
|
27
|
+
-->
|
|
21
28
|
<SelectPrimitive.Item
|
|
22
29
|
bind:ref
|
|
23
30
|
{value}
|
|
24
31
|
class={cn(
|
|
25
|
-
"relative flex h-8 w-full cursor-
|
|
32
|
+
"relative flex h-8 w-full cursor-pointer select-none items-center gap-2 rounded-lg px-2 py-1.5 ps-8 text-body outline-hidden data-[selected]:bg-brand-subtle hover:not-data-[selected]:bg-accent [[data-input-modality=keyboard]_&]:not-data-[selected]:data-[highlighted]:bg-accent [[data-input-modality=keyboard]_&]:not-data-[selected]:not-data-[highlighted]:hover:bg-transparent data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
|
26
33
|
className
|
|
27
34
|
)}
|
|
28
35
|
{...restProps}
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
<SelectPrimitive.Trigger
|
|
20
20
|
bind:ref
|
|
21
21
|
class={cn(
|
|
22
|
-
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-input bg-background px-3 py-2 text-start text-body transition-colors duration-fast hover:bg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 data-[placeholder]:text-muted-foreground aria-invalid:border-destructive [&>span]:line-clamp-1",
|
|
22
|
+
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-input bg-background px-3 py-2 text-start text-body transition-colors duration-fast hover:bg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 data-[placeholder]:text-muted-foreground aria-invalid:border-destructive [&>span]:line-clamp-1 [&[data-state=open]>svg:last-child]:rotate-180",
|
|
23
23
|
className
|
|
24
24
|
)}
|
|
25
25
|
{...restProps}
|
|
26
26
|
>
|
|
27
27
|
{@render children?.()}
|
|
28
|
-
|
|
28
|
+
<!-- Figma: the open trigger shows the arrow up. It turns with the motion of the accordion's arrow (transition-transform duration-base). -->
|
|
29
|
+
<IconChevronDown class="size-4 shrink-0 stroke-[1.333] opacity-50 transition-transform duration-base" aria-hidden="true" />
|
|
29
30
|
</SelectPrimitive.Trigger>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
aria-label={label}
|
|
25
25
|
{disabled}
|
|
26
26
|
class={cn(
|
|
27
|
-
"inline-flex size-6 shrink-0 items-center justify-center rounded-lg bg-transparent text-foreground
|
|
27
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-lg bg-transparent text-foreground hover:bg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 press-feedback",
|
|
28
28
|
className
|
|
29
29
|
)}
|
|
30
30
|
{...restProps}
|
|
@@ -18,12 +18,15 @@
|
|
|
18
18
|
<!--
|
|
19
19
|
The track is 36x20. `relative before:absolute before:inset-x-0 before:-inset-y-0.5` adds an invisible 2px above and below,
|
|
20
20
|
so the tap target is 36x24 (the minimum height of a target). Figma keeps the same 24px frame around the 20px track.
|
|
21
|
+
The thumb (the "scrub") is 16x16 and sits 2px from every edge of the track, as Figma's track pads it (2/2/2/2). The track has no
|
|
22
|
+
border of its own: a transparent 1px border here would add a pixel of padding on every side, which left the thumb 3px from the
|
|
23
|
+
start and 1px from the end.
|
|
21
24
|
-->
|
|
22
25
|
<SwitchPrimitive.Root
|
|
23
26
|
bind:ref
|
|
24
27
|
bind:checked
|
|
25
28
|
class={cn(
|
|
26
|
-
"peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full
|
|
29
|
+
"peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-input p-0.5 transition-colors duration-base ease-out before:absolute before:inset-x-0 before:-inset-y-0.5 hover:bg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:hover:bg-primary/90",
|
|
27
30
|
className
|
|
28
31
|
)}
|
|
29
32
|
{...restProps}
|
package/dist/styles/global.css
CHANGED
|
@@ -609,10 +609,11 @@ go 3 levels up in development mode, 2 levels up in production mode*/
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
/* Modal System */
|
|
612
|
+
/* The scrim of every overlay: the muted foreground token at 80%, set on the colour, with a 3px blur (Figma `Overlay/Scrim blur`) */
|
|
612
613
|
.modal-overlay {
|
|
613
614
|
@apply fixed inset-0 z-50;
|
|
614
|
-
backdrop-filter: blur(
|
|
615
|
-
background-color:
|
|
615
|
+
backdrop-filter: blur(3px);
|
|
616
|
+
background-color: color-mix(in srgb, var(--muted-foreground) 80%, transparent);
|
|
616
617
|
}
|
|
617
618
|
|
|
618
619
|
.modal-shadow {
|
package/dist/styles/tokens.css
CHANGED
|
@@ -238,8 +238,28 @@
|
|
|
238
238
|
transition-timing-function: var(--ease-drawer);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/*
|
|
242
|
+
* A pressable control shrinks to `--scale-press` while it is pressed and goes back to 100% when it is released
|
|
243
|
+
* (Figma: the Pressed variants of Button and Icon Button are 0.97 of the Default size).
|
|
244
|
+
*
|
|
245
|
+
* The utility owns the whole transition of the control: the colours that hover changes and the transform of the press.
|
|
246
|
+
* `transition-colors` sets `transition-property` too, with the same specificity, so whichever of the two came last in the
|
|
247
|
+
* stylesheet won, and when that was `transition-colors` the press had no transition at all: the control snapped in and
|
|
248
|
+
* snapped back. `:not([data-no-press])` raises the specificity above `transition-colors` and `duration-*`, so they compose
|
|
249
|
+
* in any order, and `data-no-press` is the way to opt one control out.
|
|
250
|
+
*/
|
|
241
251
|
@utility press-feedback {
|
|
242
|
-
|
|
252
|
+
&:not([data-no-press]) {
|
|
253
|
+
transition:
|
|
254
|
+
color var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
255
|
+
background-color var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
256
|
+
border-color var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
257
|
+
outline-color var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
258
|
+
text-decoration-color var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
259
|
+
fill var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
260
|
+
stroke var(--duration-fast) cubic-bezier(0.4, 0, 0.2, 1),
|
|
261
|
+
transform var(--duration-base) var(--ease-out);
|
|
262
|
+
}
|
|
243
263
|
|
|
244
264
|
&:active:not(:disabled):not([aria-disabled='true']) {
|
|
245
265
|
transform: scale(var(--scale-press));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells the styles whether the last thing a person did was press a key or use the pointer.
|
|
3
|
+
*
|
|
4
|
+
* The root element carries `data-input-modality="keyboard"` after a key press and `data-input-modality="pointer"` after the
|
|
5
|
+
* pointer moved or pressed. A list can then paint the row that the keyboard highlighted without painting, for ever, the row
|
|
6
|
+
* the pointer happened to cross last: bits-ui's Select never takes the highlight back when the pointer leaves, so what the
|
|
7
|
+
* pointer highlighted stayed painted after it had gone (Figma Select Option has Default, Hover and Selected looks, not a
|
|
8
|
+
* "was hovered" one).
|
|
9
|
+
*
|
|
10
|
+
* `installInputModality` is safe to call as often as needed: it listens once per document.
|
|
11
|
+
*/
|
|
12
|
+
/** The attribute on the root element that carries the modality. */
|
|
13
|
+
export declare const inputModalityAttribute = "data-input-modality";
|
|
14
|
+
export type InputModality = 'keyboard' | 'pointer';
|
|
15
|
+
/** Start listening in `doc` (the page by default). Does nothing on the server. */
|
|
16
|
+
export declare function installInputModality(doc?: Document | undefined): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells the styles whether the last thing a person did was press a key or use the pointer.
|
|
3
|
+
*
|
|
4
|
+
* The root element carries `data-input-modality="keyboard"` after a key press and `data-input-modality="pointer"` after the
|
|
5
|
+
* pointer moved or pressed. A list can then paint the row that the keyboard highlighted without painting, for ever, the row
|
|
6
|
+
* the pointer happened to cross last: bits-ui's Select never takes the highlight back when the pointer leaves, so what the
|
|
7
|
+
* pointer highlighted stayed painted after it had gone (Figma Select Option has Default, Hover and Selected looks, not a
|
|
8
|
+
* "was hovered" one).
|
|
9
|
+
*
|
|
10
|
+
* `installInputModality` is safe to call as often as needed: it listens once per document.
|
|
11
|
+
*/
|
|
12
|
+
/** The attribute on the root element that carries the modality. */
|
|
13
|
+
export const inputModalityAttribute = 'data-input-modality';
|
|
14
|
+
/** The documents that already listen, so that a second call does nothing. */
|
|
15
|
+
const listeningDocuments = new WeakSet();
|
|
16
|
+
/**
|
|
17
|
+
* Keys that act on a list. A modifier on its own (Shift, Meta…) or a shortcut (Cmd+C) is not the keyboard driving a list:
|
|
18
|
+
* a person who opens a menu with the pointer and then presses Shift must not see the highlight appear.
|
|
19
|
+
*/
|
|
20
|
+
function isListNavigationKey(event) {
|
|
21
|
+
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
22
|
+
return false;
|
|
23
|
+
return (event.key === 'ArrowUp' ||
|
|
24
|
+
event.key === 'ArrowDown' ||
|
|
25
|
+
event.key === 'ArrowLeft' ||
|
|
26
|
+
event.key === 'ArrowRight' ||
|
|
27
|
+
event.key === 'Home' ||
|
|
28
|
+
event.key === 'End' ||
|
|
29
|
+
event.key === 'PageUp' ||
|
|
30
|
+
event.key === 'PageDown' ||
|
|
31
|
+
event.key === 'Enter' ||
|
|
32
|
+
event.key === ' ' ||
|
|
33
|
+
event.key === 'Tab' ||
|
|
34
|
+
// Type-ahead: a single character moves the highlight to the row that starts with it
|
|
35
|
+
event.key.length === 1);
|
|
36
|
+
}
|
|
37
|
+
/** Start listening in `doc` (the page by default). Does nothing on the server. */
|
|
38
|
+
export function installInputModality(doc = typeof document === 'undefined' ? undefined : document) {
|
|
39
|
+
if (!doc || listeningDocuments.has(doc))
|
|
40
|
+
return;
|
|
41
|
+
listeningDocuments.add(doc);
|
|
42
|
+
const root = doc.documentElement;
|
|
43
|
+
const setModality = (modality) => {
|
|
44
|
+
// Compared first: the pointer moves hundreds of times a second and nothing has to be written for that
|
|
45
|
+
if (root.getAttribute(inputModalityAttribute) !== modality) {
|
|
46
|
+
root.setAttribute(inputModalityAttribute, modality);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
doc.addEventListener('keydown', (event) => {
|
|
50
|
+
if (isListNavigationKey(event))
|
|
51
|
+
setModality('keyboard');
|
|
52
|
+
}, true);
|
|
53
|
+
doc.addEventListener('pointermove', () => setModality('pointer'), { capture: true, passive: true });
|
|
54
|
+
doc.addEventListener('pointerdown', () => setModality('pointer'), { capture: true, passive: true });
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared overlay motion tokens so BaseModal, bits-ui Dialog, and related surfaces stay in sync.
|
|
3
|
+
* FilePreview / legacy modals: backdrop fades 200ms with cubicOut; the card scales from 0.95 with the same timing.
|
|
4
|
+
* Change here when Figma updates overlay motion.
|
|
5
|
+
*/
|
|
6
|
+
import { cubicOut } from 'svelte/easing';
|
|
7
|
+
/** Backdrop scrim + card motion duration (ms). Intentionally 200ms; DS `--duration-base` is 160ms for small UI. */
|
|
8
|
+
export declare const overlayTransitionDurationMs = 200;
|
|
9
|
+
/** Easing for open/close (Svelte transition easing function — matches “cubic out” in the legacy modals). */
|
|
10
|
+
export declare const overlayTransitionEasing: typeof cubicOut;
|
|
11
|
+
/**
|
|
12
|
+
* Backdrop blur (px) of the scrim behind every modal, dialog, drawer and sheet. It is the Figma effect style
|
|
13
|
+
* `Overlay/Scrim blur` (a background blur of 3).
|
|
14
|
+
*/
|
|
15
|
+
export declare const overlayBackdropBlurPx = 3;
|
|
16
|
+
/** Content scale at the start of the open transition / end of the close transition. */
|
|
17
|
+
export declare const overlayContentScaleStart = 0.95;
|
|
18
|
+
/**
|
|
19
|
+
* Tailwind duration class — must be a source literal so Tailwind's scanner emits `duration-[200ms]`.
|
|
20
|
+
* Kept in sync with `overlayTransitionDurationMs` (see tests/overlayMotionDuration.test.ts).
|
|
21
|
+
*/
|
|
22
|
+
export declare const overlayDurationClass = "duration-[200ms]";
|
|
23
|
+
/**
|
|
24
|
+
* bits-ui applies `data-[state=open]:animate-in` at specificity 0,2,0 with a 150ms default duration, which beats a
|
|
25
|
+
* plain `duration-[200ms]` (0,1,0). These state-scoped arbitrary properties match open/closed and win in cascade.
|
|
26
|
+
* Literal strings — keep in sync with `overlayTransitionDurationMs`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const overlayDialogOpenClosedAnimationDurationClasses: readonly ["data-[state=open]:[animation-duration:200ms]", "data-[state=closed]:[animation-duration:200ms]"];
|
|
29
|
+
/**
|
|
30
|
+
* The scrim colour: the muted foreground token (neutral 550 in the design system) at 80%. The opacity is part of the colour, not of
|
|
31
|
+
* the layer, because an element that is itself translucent would not blur what is behind it. Literal so Tailwind's
|
|
32
|
+
* scanner emits it; `bg-muted-foreground/80` is the Figma scrim (`Foundations / Overlay`).
|
|
33
|
+
*/
|
|
34
|
+
export declare const overlayScrimColorClass = "bg-muted-foreground/80";
|
|
35
|
+
/** Backdrop blur class — matches `overlayBackdropBlurPx`. Literal for the same reason as the colour. */
|
|
36
|
+
export declare const overlayBackdropBlurClass = "backdrop-blur-[3px]";
|
|
37
|
+
/**
|
|
38
|
+
* The whole scrim: colour and blur. Every surface that dims the page behind a panel (dialogs, modals, drawers, the
|
|
39
|
+
* navigation drawer) uses these two classes, so the overlay looks the same everywhere. Written out, not joined from the
|
|
40
|
+
* two constants above, so the scanner of Tailwind finds both classes in this one literal as well.
|
|
41
|
+
*/
|
|
42
|
+
export declare const overlayScrimClasses = "bg-muted-foreground/80 backdrop-blur-[3px]";
|
|
43
|
+
/** Tailwind arbitrary easing approximating Svelte `cubicOut` (ease-out cubic, 1 − (1 − t)³). */
|
|
44
|
+
export declare const overlayEasingClass = "ease-[cubic-bezier(0.33,1,0.68,1)]";
|
|
45
|
+
/** Overlay + panel classes for bits-ui Dialog (aligned with BaseModal motion: fade, 200ms, cubic-out-like, zoom from 0.95). */
|
|
46
|
+
export declare const overlayDialogOverlayClasses: string;
|
|
47
|
+
export declare const overlayDialogContentClasses: string;
|
|
48
|
+
/** Applied with overlay dialog classes so users who prefer reduced motion get instant open/close. */
|
|
49
|
+
export declare const overlayReducedMotionClasses = "motion-reduce:!duration-0 motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared overlay motion tokens so BaseModal, bits-ui Dialog, and related surfaces stay in sync.
|
|
3
|
+
* FilePreview / legacy modals: backdrop fades 200ms with cubicOut; the card scales from 0.95 with the same timing.
|
|
4
|
+
* Change here when Figma updates overlay motion.
|
|
5
|
+
*/
|
|
6
|
+
import { cubicOut } from 'svelte/easing';
|
|
7
|
+
/** Backdrop scrim + card motion duration (ms). Intentionally 200ms; DS `--duration-base` is 160ms for small UI. */
|
|
8
|
+
export const overlayTransitionDurationMs = 200;
|
|
9
|
+
/** Easing for open/close (Svelte transition easing function — matches “cubic out” in the legacy modals). */
|
|
10
|
+
export const overlayTransitionEasing = cubicOut;
|
|
11
|
+
/**
|
|
12
|
+
* Backdrop blur (px) of the scrim behind every modal, dialog, drawer and sheet. It is the Figma effect style
|
|
13
|
+
* `Overlay/Scrim blur` (a background blur of 3).
|
|
14
|
+
*/
|
|
15
|
+
export const overlayBackdropBlurPx = 3;
|
|
16
|
+
/** Content scale at the start of the open transition / end of the close transition. */
|
|
17
|
+
export const overlayContentScaleStart = 0.95;
|
|
18
|
+
/**
|
|
19
|
+
* Tailwind duration class — must be a source literal so Tailwind's scanner emits `duration-[200ms]`.
|
|
20
|
+
* Kept in sync with `overlayTransitionDurationMs` (see tests/overlayMotionDuration.test.ts).
|
|
21
|
+
*/
|
|
22
|
+
export const overlayDurationClass = 'duration-[200ms]';
|
|
23
|
+
/**
|
|
24
|
+
* bits-ui applies `data-[state=open]:animate-in` at specificity 0,2,0 with a 150ms default duration, which beats a
|
|
25
|
+
* plain `duration-[200ms]` (0,1,0). These state-scoped arbitrary properties match open/closed and win in cascade.
|
|
26
|
+
* Literal strings — keep in sync with `overlayTransitionDurationMs`.
|
|
27
|
+
*/
|
|
28
|
+
export const overlayDialogOpenClosedAnimationDurationClasses = [
|
|
29
|
+
'data-[state=open]:[animation-duration:200ms]',
|
|
30
|
+
'data-[state=closed]:[animation-duration:200ms]'
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* The scrim colour: the muted foreground token (neutral 550 in the design system) at 80%. The opacity is part of the colour, not of
|
|
34
|
+
* the layer, because an element that is itself translucent would not blur what is behind it. Literal so Tailwind's
|
|
35
|
+
* scanner emits it; `bg-muted-foreground/80` is the Figma scrim (`Foundations / Overlay`).
|
|
36
|
+
*/
|
|
37
|
+
export const overlayScrimColorClass = 'bg-muted-foreground/80';
|
|
38
|
+
/** Backdrop blur class — matches `overlayBackdropBlurPx`. Literal for the same reason as the colour. */
|
|
39
|
+
export const overlayBackdropBlurClass = 'backdrop-blur-[3px]';
|
|
40
|
+
/**
|
|
41
|
+
* The whole scrim: colour and blur. Every surface that dims the page behind a panel (dialogs, modals, drawers, the
|
|
42
|
+
* navigation drawer) uses these two classes, so the overlay looks the same everywhere. Written out, not joined from the
|
|
43
|
+
* two constants above, so the scanner of Tailwind finds both classes in this one literal as well.
|
|
44
|
+
*/
|
|
45
|
+
export const overlayScrimClasses = 'bg-muted-foreground/80 backdrop-blur-[3px]';
|
|
46
|
+
/** Tailwind arbitrary easing approximating Svelte `cubicOut` (ease-out cubic, 1 − (1 − t)³). */
|
|
47
|
+
export const overlayEasingClass = 'ease-[cubic-bezier(0.33,1,0.68,1)]';
|
|
48
|
+
/** Overlay + panel classes for bits-ui Dialog (aligned with BaseModal motion: fade, 200ms, cubic-out-like, zoom from 0.95). */
|
|
49
|
+
export const overlayDialogOverlayClasses = [
|
|
50
|
+
overlayScrimClasses,
|
|
51
|
+
overlayDurationClass,
|
|
52
|
+
overlayEasingClass,
|
|
53
|
+
...overlayDialogOpenClosedAnimationDurationClasses,
|
|
54
|
+
`data-[state=open]:animate-in`,
|
|
55
|
+
`data-[state=closed]:animate-out`,
|
|
56
|
+
`data-[state=closed]:fade-out-0`,
|
|
57
|
+
`data-[state=open]:fade-in-0`
|
|
58
|
+
].join(' ');
|
|
59
|
+
export const overlayDialogContentClasses = [
|
|
60
|
+
overlayDurationClass,
|
|
61
|
+
overlayEasingClass,
|
|
62
|
+
...overlayDialogOpenClosedAnimationDurationClasses,
|
|
63
|
+
`data-[state=open]:animate-in`,
|
|
64
|
+
`data-[state=closed]:animate-out`,
|
|
65
|
+
`data-[state=closed]:fade-out-0`,
|
|
66
|
+
`data-[state=open]:fade-in-0`,
|
|
67
|
+
`data-[state=closed]:zoom-out-95`,
|
|
68
|
+
`data-[state=open]:zoom-in-95`
|
|
69
|
+
].join(' ');
|
|
70
|
+
/** Applied with overlay dialog classes so users who prefer reduced motion get instant open/close. */
|
|
71
|
+
export const overlayReducedMotionClasses = 'motion-reduce:!duration-0 motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none';
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/** Footer links in SettingsDialog (Figma Settings Dialog 107:618). */
|
|
2
|
-
export interface SettingsDialogMessages {
|
|
3
|
-
privacyPolicy: string;
|
|
4
|
-
termsOfService: string;
|
|
5
|
-
cookieSettings: string;
|
|
6
|
-
}
|
|
7
|
-
export declare const defaultSettingsDialogMessages: SettingsDialogMessages;
|