@passui/react 0.1.44 → 0.1.46
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 +57 -46
- package/dist/types/src/components/accordion/accordion.d.ts +1 -1
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
- package/dist/types/src/components/popover/index.d.ts +41 -0
- package/dist/types/src/components/popover/popover-content.d.ts +32 -0
- package/dist/types/src/components/popover/popover-root.d.ts +22 -0
- package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
- package/dist/types/src/components/table/table.d.ts +1 -1
- package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/machines/popover.machine.d.ts +30 -0
- package/package.json +307 -307
- package/src/appearance/index.ts +2 -2
- package/src/appearance/platform.tsx +76 -60
- package/src/appearance/theme.tsx +409 -377
- package/src/components/accordion/accordion.tsx +58 -49
- package/src/components/ambient/ambient.tsx +25 -25
- package/src/components/ambient/index.ts +1 -1
- package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
- package/src/components/breadcrumb/breadcrumb.tsx +63 -57
- package/src/components/button/button-styles.ts +39 -32
- package/src/components/button/button.tsx +91 -84
- package/src/components/button/icon-button.tsx +27 -25
- package/src/components/button/pending-spinner.tsx +8 -8
- package/src/components/button/use-pending-indicator.ts +11 -11
- package/src/components/chip/chip.tsx +35 -30
- package/src/components/code-block/code-block.tsx +40 -39
- package/src/components/combo-box/combo-box.tsx +5 -5
- package/src/components/command/command.tsx +124 -94
- package/src/components/compact-date-picker/calendar-month.ts +75 -53
- package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
- package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
- package/src/components/compact-time-picker/time-value.ts +32 -29
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
- package/src/components/content-view/content-view.tsx +168 -159
- package/src/components/content-view/index.ts +1 -1
- package/src/components/context-menu/conceal-menu-source.ts +25 -25
- package/src/components/context-menu/context-menu-host.tsx +439 -398
- package/src/components/context-menu/context-menu-store.ts +125 -123
- package/src/components/context-menu/menu-navigation.ts +25 -18
- package/src/components/context-menu/menu-position.ts +33 -29
- package/src/components/context-menu/menu-reveal.ts +32 -32
- package/src/components/context-menu/use-context-menu.ts +63 -56
- package/src/components/conversation/conversation.tsx +265 -263
- package/src/components/conversation/index.ts +1 -1
- package/src/components/date-picker/date-picker.tsx +95 -94
- package/src/components/date-picker/date-value.ts +20 -10
- package/src/components/dialog/dialog-host.tsx +105 -103
- package/src/components/dialog/dialog-store.ts +37 -36
- package/src/components/dialog/dialog.tsx +192 -179
- package/src/components/dialog/index.ts +1 -1
- package/src/components/disclosure-group/disclosure-group.tsx +93 -86
- package/src/components/divider/divider.tsx +24 -22
- package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
- package/src/components/labeled-content/labeled-content.tsx +68 -68
- package/src/components/locale-switcher/index.ts +1 -1
- package/src/components/locale-switcher/locale-switcher.tsx +279 -251
- package/src/components/menu/menu.tsx +75 -58
- package/src/components/morph/index.ts +2 -2
- package/src/components/morph/morph.tsx +227 -201
- package/src/components/morph/use-morph-disclosure.ts +19 -19
- package/src/components/one-time-code/one-time-code.tsx +216 -200
- package/src/components/picker/picker-trigger.ts +16 -13
- package/src/components/picker/picker.tsx +371 -345
- package/src/components/popover/anchored-surface.tsx +253 -229
- package/src/components/popover/index.ts +50 -0
- package/src/components/popover/popover-content.tsx +334 -0
- package/src/components/popover/popover-fit.ts +58 -53
- package/src/components/popover/popover-root.tsx +97 -0
- package/src/components/popover/popover-trigger.tsx +71 -0
- package/src/components/profile-image/profile-image.tsx +80 -64
- package/src/components/progress-view/progress-view.tsx +61 -59
- package/src/components/progress-view/use-liquid-progress.ts +38 -38
- package/src/components/radio-group/radio-group.tsx +71 -68
- package/src/components/search-field/expanding-search-field.tsx +191 -177
- package/src/components/search-field/search-field.tsx +59 -50
- package/src/components/search-list/search-list.tsx +246 -236
- package/src/components/section/section.tsx +58 -58
- package/src/components/secure-field/secure-field.tsx +3 -3
- package/src/components/segmented-control/segmented-control.tsx +136 -136
- package/src/components/sheet/modal-environment.ts +71 -64
- package/src/components/sheet/sheet-portal.tsx +58 -53
- package/src/components/sheet/sheet.tsx +146 -141
- package/src/components/sheet/use-sheet-focus.ts +97 -82
- package/src/components/sidebar/sidebar.tsx +78 -78
- package/src/components/skeleton/skeleton.tsx +30 -21
- package/src/components/slider/slider.tsx +113 -104
- package/src/components/slosh-slider/liquid-step.ts +32 -31
- package/src/components/slosh-slider/slosh-slider.tsx +265 -228
- package/src/components/stat-tile/index.ts +1 -1
- package/src/components/stat-tile/stat-tile.tsx +79 -68
- package/src/components/status-indicator/status-indicator.tsx +40 -37
- package/src/components/stepper/rolling-digit.tsx +82 -68
- package/src/components/stepper/stepper.tsx +122 -113
- package/src/components/swatch-picker/swatch-picker.tsx +120 -120
- package/src/components/tab-bar/tab-bar.tsx +149 -148
- package/src/components/tab-view/tab-view.tsx +140 -137
- package/src/components/table/table.tsx +117 -98
- package/src/components/text-editor/text-editor.tsx +34 -29
- package/src/components/text-field/text-field.tsx +182 -180
- package/src/components/toast/toast-store.ts +127 -115
- package/src/components/toast/toaster.tsx +361 -347
- package/src/components/toggle/toggle.tsx +297 -282
- package/src/components/tooltip/tooltip-host.tsx +157 -150
- package/src/components/view-controller/index.ts +1 -1
- package/src/components/view-controller/view-controller.tsx +64 -53
- package/src/components/wake-slider/wake-slider.tsx +220 -205
- package/src/hooks/scroll/minimize-decision.ts +11 -7
- package/src/hooks/use-anchored-disclosure.ts +19 -19
- package/src/hooks/use-anchored-placement.ts +29 -25
- package/src/hooks/use-dismiss.ts +33 -29
- package/src/hooks/use-floating-dismiss.ts +24 -22
- package/src/hooks/use-media-query.ts +67 -60
- package/src/hooks/use-minimize-on-scroll.ts +69 -60
- package/src/hooks/use-visual-viewport.ts +63 -63
- package/src/hooks/viewport/types.ts +16 -16
- package/src/hooks/viewport/viewport-metrics.ts +16 -10
- package/src/icons/icons.tsx +440 -328
- package/src/index.ts +91 -91
- package/src/lib/anchored-placement.ts +70 -65
- package/src/lib/class-names.ts +14 -13
- package/src/lib/clipboard.ts +8 -7
- package/src/lib/date.ts +61 -60
- package/src/lib/floating-layers.ts +39 -24
- package/src/lib/haptics.ts +10 -10
- package/src/lib/language.ts +9 -9
- package/src/lib/range-value.ts +27 -21
- package/src/lib/screen-edges.ts +33 -30
- package/src/lib/shapes.ts +38 -34
- package/src/machines/popover.machine.ts +51 -0
- package/src/motion/transitions.ts +46 -43
- package/src/styles/code-block.css +69 -43
- package/src/styles/motion-controls.css +393 -191
- package/src/tokens.ts +1 -1
- package/dist/types/src/components/popover/popover.d.ts +0 -21
- package/src/components/popover/popover.tsx +0 -322
|
@@ -1,61 +1,70 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DisclosureGroup,
|
|
4
|
+
type DisclosureGroupSize,
|
|
5
|
+
} from "../disclosure-group/disclosure-group";
|
|
6
|
+
import { cn } from "../../lib/class-names";
|
|
4
7
|
|
|
5
|
-
export type AccordionSize = DisclosureGroupSize
|
|
8
|
+
export type AccordionSize = DisclosureGroupSize;
|
|
6
9
|
|
|
7
10
|
const containerRadius: Record<AccordionSize, string> = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
11
|
+
sm: "rounded-xl",
|
|
12
|
+
md: "rounded-2xl",
|
|
13
|
+
lg: "rounded-3xl",
|
|
14
|
+
};
|
|
12
15
|
|
|
13
16
|
export interface AccordionItem {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/** Stable value used to identify the expanded item. */
|
|
18
|
+
value: string;
|
|
19
|
+
title: string;
|
|
20
|
+
content: ReactNode;
|
|
21
|
+
description?: string;
|
|
22
|
+
disabled?: boolean;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export interface AccordionProps {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
items: readonly AccordionItem[];
|
|
27
|
+
/** The expanded item, or `null` when all items are closed. */
|
|
28
|
+
value: string | null;
|
|
29
|
+
/** Selecting the open item reports `null`, so every item can collapse. */
|
|
30
|
+
onValueChange: (value: string | null) => void;
|
|
31
|
+
/** Density passed through to each DisclosureGroup. @default "md" */
|
|
32
|
+
size?: AccordionSize;
|
|
33
|
+
className?: string;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/** A single-open, collapsible accordion with labelled animated content regions. */
|
|
34
|
-
export function Accordion({
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
export function Accordion({
|
|
38
|
+
items,
|
|
39
|
+
value,
|
|
40
|
+
onValueChange,
|
|
41
|
+
size = "md",
|
|
42
|
+
className,
|
|
43
|
+
}: AccordionProps) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className={cn(
|
|
47
|
+
"flex w-full min-w-0 flex-col overflow-hidden",
|
|
48
|
+
containerRadius[size],
|
|
49
|
+
"border-separator/60 bg-elevated border",
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{items.map((item, index) => (
|
|
54
|
+
<DisclosureGroup
|
|
55
|
+
key={item.value}
|
|
56
|
+
label={item.title}
|
|
57
|
+
description={item.description}
|
|
58
|
+
open={value === item.value}
|
|
59
|
+
disabled={item.disabled}
|
|
60
|
+
size={size}
|
|
61
|
+
triggerClassName="rounded-none"
|
|
62
|
+
onOpenChange={(open) => onValueChange(open ? item.value : null)}
|
|
63
|
+
className={cn(index > 0 && "border-separator/60 border-t")}
|
|
64
|
+
>
|
|
65
|
+
{item.content}
|
|
66
|
+
</DisclosureGroup>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
61
70
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ComponentPropsWithoutRef } from "react"
|
|
2
|
-
import { cn } from "../../lib/class-names"
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { cn } from "../../lib/class-names";
|
|
3
3
|
|
|
4
4
|
type AmbientProps = ComponentPropsWithoutRef<"section"> & {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
5
|
+
/** The image whose colors glow behind the content. */
|
|
6
|
+
src?: string | null;
|
|
7
|
+
};
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Content with its image's colors glowing softly behind it, no card or
|
|
@@ -12,24 +12,24 @@ type AmbientProps = ComponentPropsWithoutRef<"section"> & {
|
|
|
12
12
|
* fades into the page, the way Apple lets a photo light its surroundings.
|
|
13
13
|
*/
|
|
14
14
|
export function Ambient({ src, className, children, ...props }: AmbientProps) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
return (
|
|
16
|
+
<section className={cn("relative isolate", className)} {...props}>
|
|
17
|
+
{src && (
|
|
18
|
+
<img
|
|
19
|
+
aria-hidden
|
|
20
|
+
src={src}
|
|
21
|
+
alt=""
|
|
22
|
+
className={cn(
|
|
23
|
+
"pointer-events-none absolute top-[38%] left-1/2 -z-10 size-[min(32rem,100vw)] -translate-x-1/2 -translate-y-1/2",
|
|
24
|
+
"rounded-full object-cover opacity-20 blur-3xl",
|
|
25
|
+
// on a dark page the same light blooms far wider, so it shrinks and dims
|
|
26
|
+
"dark:size-[min(20rem,80vw)] dark:opacity-15 dark:blur-2xl",
|
|
27
|
+
"mask-[radial-gradient(closest-side,black_35%,transparent)]",
|
|
28
|
+
"animate-in fade-in-0 zoom-in-90 duration-1000",
|
|
29
|
+
)}
|
|
30
|
+
/>
|
|
31
|
+
)}
|
|
32
|
+
{children}
|
|
33
|
+
</section>
|
|
34
|
+
);
|
|
35
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./ambient"
|
|
1
|
+
export * from "./ambient";
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import type { HTMLAttributes } from "react"
|
|
2
|
-
import { cn } from "../../lib/class-names"
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
import { cn } from "../../lib/class-names";
|
|
3
3
|
|
|
4
4
|
export interface AspectRatioProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/** Width divided by height. @default 1.3333333333333333 */
|
|
6
|
+
ratio?: number;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/** A crop frame: size its geometry, while images cover without distortion. */
|
|
10
|
-
export function AspectRatio({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
export function AspectRatio({
|
|
11
|
+
ratio = 4 / 3,
|
|
12
|
+
className,
|
|
13
|
+
style,
|
|
14
|
+
...props
|
|
15
|
+
}: AspectRatioProps) {
|
|
16
|
+
const aspect = Number.isFinite(ratio) && ratio > 0 ? ratio : 4 / 3;
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
{...props}
|
|
20
|
+
className={cn(
|
|
21
|
+
"relative w-full overflow-hidden [&>img]:size-full [&>img]:object-cover [&>video]:size-full [&>video]:object-cover",
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
style={{ aspectRatio: aspect, ...style }}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
22
27
|
}
|
|
@@ -1,67 +1,73 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { cn } from "../../lib/class-names"
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../../lib/class-names";
|
|
3
3
|
|
|
4
|
-
export type BreadcrumbSize = "sm" | "md"
|
|
4
|
+
export type BreadcrumbSize = "sm" | "md";
|
|
5
5
|
|
|
6
6
|
const sizes: Record<BreadcrumbSize, string> = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
7
|
+
sm: "text-caption",
|
|
8
|
+
md: "text-footnote",
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
export interface BreadcrumbItem {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
label: string;
|
|
13
|
+
href?: string;
|
|
14
14
|
}
|
|
15
15
|
export interface BreadcrumbProps {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
label: string;
|
|
17
|
+
items: readonly BreadcrumbItem[];
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Integrate a client router without nesting interactive elements. */
|
|
20
|
+
renderLink?: (item: BreadcrumbItem & { href: string }) => ReactNode;
|
|
21
|
+
/** Text scale. @default "md" */
|
|
22
|
+
size?: BreadcrumbSize;
|
|
23
23
|
}
|
|
24
24
|
/** Ancestors stay links; the final item identifies the current page. */
|
|
25
|
-
export function Breadcrumb({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
export function Breadcrumb({
|
|
26
|
+
label,
|
|
27
|
+
items,
|
|
28
|
+
className,
|
|
29
|
+
renderLink,
|
|
30
|
+
size = "md",
|
|
31
|
+
}: BreadcrumbProps) {
|
|
32
|
+
return (
|
|
33
|
+
<nav aria-label={label} className={cn("not-typeset", className)}>
|
|
34
|
+
<ol
|
|
35
|
+
className={cn(
|
|
36
|
+
"text-label-secondary m-0 flex list-none flex-wrap items-center gap-x-2 gap-y-1 p-0",
|
|
37
|
+
sizes[size],
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
{items.map((item, index) => (
|
|
41
|
+
<li
|
|
42
|
+
key={item.label}
|
|
43
|
+
className="m-0 flex min-w-0 items-center gap-2 p-0"
|
|
44
|
+
>
|
|
45
|
+
{index > 0 && (
|
|
46
|
+
<span aria-hidden="true" className="text-label-tertiary">
|
|
47
|
+
/
|
|
48
|
+
</span>
|
|
49
|
+
)}
|
|
50
|
+
{index === items.length - 1 ? (
|
|
51
|
+
<span aria-current="page" className="text-label font-medium">
|
|
52
|
+
{item.label}
|
|
53
|
+
</span>
|
|
54
|
+
) : item.href ? (
|
|
55
|
+
(renderLink?.({ ...item, href: item.href }) ?? (
|
|
56
|
+
<a
|
|
57
|
+
href={item.href}
|
|
58
|
+
className={cn(
|
|
59
|
+
"hover:text-label focus-visible:outline-accent-text rounded-sm focus-visible:outline-2",
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{item.label}
|
|
63
|
+
</a>
|
|
64
|
+
))
|
|
65
|
+
) : (
|
|
66
|
+
<span>{item.label}</span>
|
|
67
|
+
)}
|
|
68
|
+
</li>
|
|
69
|
+
))}
|
|
70
|
+
</ol>
|
|
71
|
+
</nav>
|
|
72
|
+
);
|
|
67
73
|
}
|
|
@@ -1,42 +1,49 @@
|
|
|
1
1
|
export type ButtonVariant =
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
| "default"
|
|
3
|
+
| "destructive"
|
|
4
|
+
| "outline"
|
|
5
|
+
| "secondary"
|
|
6
|
+
| "ghost"
|
|
7
|
+
| "link"
|
|
8
|
+
| "accent";
|
|
9
9
|
|
|
10
10
|
/** Icon-only buttons use the same sizing contract as Button. */
|
|
11
11
|
export const iconButtonSizes = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} as const
|
|
12
|
+
icon: "size-11 p-0 rounded-full",
|
|
13
|
+
"icon-xs": "size-8 p-0 rounded-full",
|
|
14
|
+
"icon-sm": "size-9 p-0 rounded-full",
|
|
15
|
+
"icon-lg": "size-12 p-0 rounded-full",
|
|
16
|
+
} as const;
|
|
17
17
|
|
|
18
|
-
export type IconButtonSize = keyof typeof iconButtonSizes
|
|
18
|
+
export type IconButtonSize = keyof typeof iconButtonSizes;
|
|
19
19
|
|
|
20
|
-
export type ButtonSize =
|
|
20
|
+
export type ButtonSize =
|
|
21
|
+
| "default"
|
|
22
|
+
| "xs"
|
|
23
|
+
| "sm"
|
|
24
|
+
| "lg"
|
|
25
|
+
| "icon-full"
|
|
26
|
+
| IconButtonSize;
|
|
21
27
|
|
|
22
28
|
export const buttonVariants: Record<ButtonVariant, string> = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
default:
|
|
30
|
+
"bg-inverse text-on-inverse shadow-[inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.18),0_6px_16px_-6px_rgb(0_0_0/0.35)] hover:brightness-[1.12]",
|
|
31
|
+
destructive:
|
|
32
|
+
"bg-danger text-background shadow-[inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.18),0_6px_16px_-6px_rgb(0_0_0/0.35)] hover:brightness-[1.08]",
|
|
33
|
+
outline: "border border-separator bg-transparent text-label hover:bg-fill/60",
|
|
34
|
+
secondary:
|
|
35
|
+
"bg-elevated text-label shadow-[inset_0_0_0_0.5px_color-mix(in_srgb,var(--color-separator)_55%,transparent),0_1px_2px_rgb(40_30_0/0.06)] hover:bg-fill/60",
|
|
36
|
+
ghost: "bg-transparent text-label hover:bg-fill/80",
|
|
37
|
+
link: "bg-transparent text-accent-text underline-offset-4 hover:underline",
|
|
38
|
+
accent:
|
|
39
|
+
"bg-accent text-on-accent ring-1 ring-accent-text/25 shadow-[inset_0_1px_0_rgb(255_255_255/0.35),0_1px_2px_rgb(60_40_0/0.16),0_6px_16px_-6px_color-mix(in_srgb,var(--color-accent)_70%,transparent)] hover:brightness-[1.04]",
|
|
40
|
+
};
|
|
34
41
|
|
|
35
42
|
export const buttonSizes: Record<ButtonSize, string> = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
+
default: "h-11 px-5 text-subheadline rounded-full",
|
|
44
|
+
xs: "h-8 px-3 text-caption rounded-full",
|
|
45
|
+
sm: "h-9 px-4 text-subheadline rounded-full",
|
|
46
|
+
lg: "h-12 px-6 text-body rounded-full",
|
|
47
|
+
...iconButtonSizes,
|
|
48
|
+
"icon-full": "h-11 w-full px-5 text-subheadline rounded-full",
|
|
49
|
+
};
|
|
@@ -1,100 +1,107 @@
|
|
|
1
|
-
import { type ButtonHTMLAttributes, forwardRef } from "react"
|
|
2
|
-
import { cn } from "../../lib/class-names"
|
|
3
|
-
import { PendingSpinner } from "./pending-spinner"
|
|
4
|
-
import { usePendingIndicator } from "./use-pending-indicator"
|
|
5
|
-
import {
|
|
1
|
+
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
|
2
|
+
import { cn } from "../../lib/class-names";
|
|
3
|
+
import { PendingSpinner } from "./pending-spinner";
|
|
4
|
+
import { usePendingIndicator } from "./use-pending-indicator";
|
|
5
|
+
import {
|
|
6
|
+
buttonSizes,
|
|
7
|
+
buttonVariants,
|
|
8
|
+
type ButtonSize,
|
|
9
|
+
type ButtonVariant,
|
|
10
|
+
} from "./button-styles";
|
|
6
11
|
|
|
7
|
-
export type { ButtonSize, ButtonVariant }
|
|
12
|
+
export type { ButtonSize, ButtonVariant };
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* Each variant has a little depth: a fine highlight along the top edge and
|
|
11
16
|
* a soft, layered shadow, like a well-made physical key.
|
|
12
17
|
*/
|
|
13
18
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Visual emphasis: `default` (solid), `accent` (main action),
|
|
21
|
+
* `secondary` (quiet fill), `outline`, `ghost`, `link`, or `destructive`.
|
|
22
|
+
* @default "default"
|
|
23
|
+
*/
|
|
24
|
+
variant?: ButtonVariant;
|
|
25
|
+
/**
|
|
26
|
+
* Label sizes: `xs`, `sm`, `default`, `lg`.
|
|
27
|
+
* Icon sizes: `icon-xs`, `icon-sm`, `icon`, `icon-lg`, `icon-full`.
|
|
28
|
+
* Prefer IconButton for an icon with a built-in accessible label.
|
|
29
|
+
* @default "default"
|
|
30
|
+
*/
|
|
31
|
+
size?: ButtonSize;
|
|
32
|
+
/** Shows a delayed spinner, keeps the button's width, and prevents repeat presses.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
isPending?: boolean;
|
|
36
|
+
/** Layout classes for the content wrapper, such as a full-width disclosure row. */
|
|
37
|
+
contentClassName?: string;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/** Shares native button appearance with semantic links without nesting a button. */
|
|
36
41
|
export function buttonStyle({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
variant = "default",
|
|
43
|
+
size = "default",
|
|
44
|
+
className,
|
|
40
45
|
}: Pick<ButtonProps, "variant" | "size" | "className"> = {}) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
return cn(
|
|
47
|
+
"group relative inline-flex shrink-0 items-center justify-center gap-2 overflow-hidden font-semibold tracking-[-0.005em] whitespace-nowrap select-none",
|
|
48
|
+
"transition-[background-color,filter,transform,box-shadow] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] active:scale-[0.98]",
|
|
49
|
+
"disabled:pointer-events-none disabled:opacity-40 motion-reduce:transform-none motion-reduce:transition-none",
|
|
50
|
+
"focus-visible:ring-accent/70 focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
51
|
+
buttonSizes[size],
|
|
52
|
+
buttonVariants[variant],
|
|
53
|
+
className,
|
|
54
|
+
);
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/** A native button with Passui styling, pending feedback, and an HTMLButtonElement ref. */
|
|
53
|
-
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
59
|
+
function Button(
|
|
60
|
+
{
|
|
61
|
+
variant = "default",
|
|
62
|
+
size = "default",
|
|
63
|
+
isPending = false,
|
|
64
|
+
contentClassName,
|
|
65
|
+
disabled,
|
|
66
|
+
children,
|
|
67
|
+
className,
|
|
68
|
+
type = "button",
|
|
69
|
+
...props
|
|
70
|
+
},
|
|
71
|
+
ref,
|
|
72
|
+
) {
|
|
73
|
+
const showPendingIndicator = usePendingIndicator(isPending);
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
ref={ref}
|
|
78
|
+
type={type}
|
|
79
|
+
className={buttonStyle({
|
|
80
|
+
variant,
|
|
81
|
+
size,
|
|
82
|
+
// pending is busy, not unavailable: keep full colour
|
|
83
|
+
className: cn(
|
|
84
|
+
"data-pending:cursor-progress data-pending:disabled:opacity-100",
|
|
85
|
+
className,
|
|
86
|
+
),
|
|
87
|
+
})}
|
|
88
|
+
{...props}
|
|
89
|
+
data-pending={isPending || undefined}
|
|
90
|
+
disabled={disabled || isPending}
|
|
91
|
+
aria-busy={isPending || props["aria-busy"]}
|
|
92
|
+
>
|
|
93
|
+
{/* the label keeps its space, so the width never jumps */}
|
|
94
|
+
<span
|
|
95
|
+
className={cn(
|
|
96
|
+
"inline-flex min-w-0 items-center justify-center gap-2 truncate transition-opacity duration-200",
|
|
97
|
+
showPendingIndicator && "opacity-0",
|
|
98
|
+
contentClassName,
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</span>
|
|
103
|
+
{showPendingIndicator && <PendingSpinner />}
|
|
104
|
+
</button>
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
);
|