@passui/react 0.1.19 → 0.1.20
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 +38 -0
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/dist/types/src/components/accordion/accordion.d.ts +19 -0
- package/dist/types/src/components/aspect-ratio/aspect-ratio.d.ts +7 -0
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +16 -0
- package/dist/types/src/components/button/button.d.ts +2 -0
- package/dist/types/src/components/code-block/code-block.d.ts +10 -0
- package/dist/types/src/components/combo-box/combo-box.d.ts +9 -0
- package/dist/types/src/components/command/command.d.ts +18 -0
- package/dist/types/src/components/content-unavailable-view/content-unavailable-view.d.ts +9 -0
- package/dist/types/src/components/context-menu/context-menu-store.d.ts +2 -2
- package/dist/types/src/components/context-menu/menu-reveal.d.ts +3 -8
- package/dist/types/src/components/dialog/dialog-store.d.ts +3 -1
- package/dist/types/src/components/disclosure-group/disclosure-group.d.ts +3 -1
- package/dist/types/src/components/keyboard-shortcut/keyboard-shortcut.d.ts +5 -0
- package/dist/types/src/components/menu/menu.d.ts +1 -1
- package/dist/types/src/components/one-time-code/one-time-code.d.ts +18 -0
- package/dist/types/src/components/picker/picker.d.ts +7 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +13 -0
- package/dist/types/src/components/popover/popover.d.ts +5 -1
- package/dist/types/src/components/progress-view/progress-view.d.ts +1 -1
- package/dist/types/src/components/progress-view/use-liquid-progress.d.ts +3 -0
- package/dist/types/src/components/radio-group/radio-group.d.ts +17 -0
- package/dist/types/src/components/search-field/expanding-search-field.d.ts +5 -0
- package/dist/types/src/components/search-field/search-field.d.ts +5 -1
- package/dist/types/src/components/sidebar/sidebar.d.ts +3 -2
- package/dist/types/src/components/skeleton/skeleton.d.ts +6 -0
- package/dist/types/src/components/slosh-slider/liquid-step.d.ts +6 -0
- package/dist/types/src/components/slosh-slider/slosh-slider.d.ts +18 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +11 -0
- package/dist/types/src/components/text-field/text-field.d.ts +3 -1
- package/dist/types/src/components/wake-slider/wake-slider.d.ts +12 -0
- package/dist/types/src/hooks/use-anchored-disclosure.d.ts +9 -0
- package/dist/types/src/hooks/use-anchored-placement.d.ts +1 -1
- package/dist/types/src/index.d.ts +13 -0
- package/dist/types/src/lib/anchored-placement.d.ts +2 -2
- package/package.json +60 -4
- package/src/components/accordion/accordion.tsx +48 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/breadcrumb/breadcrumb.tsx +53 -0
- package/src/components/button/button.tsx +5 -1
- package/src/components/code-block/code-block.tsx +41 -0
- package/src/components/combo-box/combo-box.tsx +13 -0
- package/src/components/command/command.tsx +119 -0
- package/src/components/compact-date-picker/compact-date-picker.tsx +40 -84
- package/src/components/compact-time-picker/compact-time-picker.tsx +44 -81
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +37 -0
- package/src/components/context-menu/context-menu-host.tsx +28 -24
- package/src/components/context-menu/context-menu-store.ts +3 -4
- package/src/components/context-menu/menu-reveal.ts +5 -2
- package/src/components/dialog/dialog-host.tsx +13 -7
- package/src/components/dialog/dialog-store.ts +15 -4
- package/src/components/disclosure-group/disclosure-group.tsx +19 -10
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +17 -0
- package/src/components/menu/menu.tsx +1 -1
- package/src/components/one-time-code/one-time-code.tsx +168 -0
- package/src/components/picker/picker.tsx +121 -108
- package/src/components/popover/anchored-surface.tsx +223 -0
- package/src/components/popover/popover.tsx +149 -20
- package/src/components/progress-view/progress-view.tsx +6 -1
- package/src/components/progress-view/use-liquid-progress.ts +43 -0
- package/src/components/radio-group/radio-group.tsx +63 -0
- package/src/components/search-field/expanding-search-field.tsx +183 -0
- package/src/components/search-field/search-field.tsx +41 -25
- package/src/components/search-list/search-list.tsx +16 -7
- package/src/components/sidebar/sidebar.tsx +21 -12
- package/src/components/skeleton/skeleton.tsx +19 -0
- package/src/components/slosh-slider/liquid-step.ts +42 -0
- package/src/components/slosh-slider/slosh-slider.tsx +251 -0
- package/src/components/stepper/rolling-digit.tsx +77 -0
- package/src/components/stepper/stepper.tsx +50 -29
- package/src/components/text-field/text-field.tsx +45 -7
- package/src/components/toggle/toggle.tsx +147 -30
- package/src/components/wake-slider/wake-slider.tsx +237 -0
- package/src/hooks/use-anchored-disclosure.ts +23 -0
- package/src/hooks/use-anchored-placement.ts +7 -4
- package/src/icons/icons.tsx +150 -150
- package/src/index.ts +18 -0
- package/src/lib/anchored-placement.ts +7 -3
- package/src/styles/code-block.css +33 -0
- package/src/styles/motion-controls.css +189 -0
- package/src/styles/styles.css +3 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { DisclosureGroup } from "../disclosure-group/disclosure-group"
|
|
3
|
+
import { cn } from "../../lib/class-names"
|
|
4
|
+
|
|
5
|
+
export interface AccordionItem {
|
|
6
|
+
/** Stable value used to identify the expanded item. */
|
|
7
|
+
value: string
|
|
8
|
+
title: string
|
|
9
|
+
content: ReactNode
|
|
10
|
+
description?: string
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AccordionProps {
|
|
15
|
+
items: readonly AccordionItem[]
|
|
16
|
+
/** The expanded item, or `null` when all items are closed. */
|
|
17
|
+
value: string | null
|
|
18
|
+
/** Selecting the open item reports `null`, so every item can collapse. */
|
|
19
|
+
onValueChange: (value: string | null) => void
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A single-open, collapsible accordion with labelled animated content regions. */
|
|
24
|
+
export function Accordion({ items, value, onValueChange, className }: AccordionProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
"flex w-full min-w-0 flex-col overflow-hidden rounded-2xl border border-separator/60 bg-elevated",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
{items.map((item, index) => (
|
|
33
|
+
<DisclosureGroup
|
|
34
|
+
key={item.value}
|
|
35
|
+
label={item.title}
|
|
36
|
+
description={item.description}
|
|
37
|
+
open={value === item.value}
|
|
38
|
+
disabled={item.disabled}
|
|
39
|
+
triggerClassName="rounded-none"
|
|
40
|
+
onOpenChange={(open) => onValueChange(open ? item.value : null)}
|
|
41
|
+
className={cn(index > 0 && "border-t border-separator/60")}
|
|
42
|
+
>
|
|
43
|
+
{item.content}
|
|
44
|
+
</DisclosureGroup>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react"
|
|
2
|
+
import { cn } from "../../lib/class-names"
|
|
3
|
+
|
|
4
|
+
export interface AspectRatioProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/** Width divided by height. @default 1.3333333333333333 */
|
|
6
|
+
ratio?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A crop frame: size its geometry, while images cover without distortion. */
|
|
10
|
+
export function AspectRatio({ ratio = 4 / 3, className, style, ...props }: AspectRatioProps) {
|
|
11
|
+
const aspect = Number.isFinite(ratio) && ratio > 0 ? ratio : 4 / 3
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
{...props}
|
|
15
|
+
className={cn(
|
|
16
|
+
"relative w-full overflow-hidden [&>img]:size-full [&>img]:object-cover [&>video]:size-full [&>video]:object-cover",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
style={{ aspectRatio: aspect, ...style }}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { cn } from "../../lib/class-names"
|
|
3
|
+
|
|
4
|
+
export interface BreadcrumbItem {
|
|
5
|
+
label: string
|
|
6
|
+
href?: string
|
|
7
|
+
}
|
|
8
|
+
export interface BreadcrumbProps {
|
|
9
|
+
label: string
|
|
10
|
+
items: readonly BreadcrumbItem[]
|
|
11
|
+
className?: string
|
|
12
|
+
/** Integrate a client router without nesting interactive elements. */
|
|
13
|
+
renderLink?: (item: BreadcrumbItem & { href: string }) => ReactNode
|
|
14
|
+
}
|
|
15
|
+
/** Ancestors stay links; the final item identifies the current page. */
|
|
16
|
+
export function Breadcrumb({ label, items, className, renderLink }: BreadcrumbProps) {
|
|
17
|
+
return (
|
|
18
|
+
<nav aria-label={label} className={cn("not-typeset", className)}>
|
|
19
|
+
<ol className="m-0 flex list-none flex-wrap items-center gap-x-2 gap-y-1 p-0 text-footnote text-label-secondary">
|
|
20
|
+
{items.map((item, index) => (
|
|
21
|
+
<li
|
|
22
|
+
key={`${index}-${item.label}`}
|
|
23
|
+
className="m-0 flex min-w-0 items-center gap-2 p-0"
|
|
24
|
+
>
|
|
25
|
+
{index > 0 && (
|
|
26
|
+
<span aria-hidden="true" className="text-label-tertiary">
|
|
27
|
+
/
|
|
28
|
+
</span>
|
|
29
|
+
)}
|
|
30
|
+
{index === items.length - 1 ? (
|
|
31
|
+
<span aria-current="page" className="font-medium text-label">
|
|
32
|
+
{item.label}
|
|
33
|
+
</span>
|
|
34
|
+
) : item.href ? (
|
|
35
|
+
(renderLink?.({ ...item, href: item.href }) ?? (
|
|
36
|
+
<a
|
|
37
|
+
href={item.href}
|
|
38
|
+
className={cn(
|
|
39
|
+
"rounded-sm hover:text-label focus-visible:outline-2 focus-visible:outline-accent-text"
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{item.label}
|
|
43
|
+
</a>
|
|
44
|
+
))
|
|
45
|
+
) : (
|
|
46
|
+
<span>{item.label}</span>
|
|
47
|
+
)}
|
|
48
|
+
</li>
|
|
49
|
+
))}
|
|
50
|
+
</ol>
|
|
51
|
+
</nav>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -28,6 +28,8 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
28
28
|
* @default false
|
|
29
29
|
*/
|
|
30
30
|
isPending?: boolean
|
|
31
|
+
/** Layout classes for the content wrapper, such as a full-width disclosure row. */
|
|
32
|
+
contentClassName?: string
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/** Shares native button appearance with semantic links without nesting a button. */
|
|
@@ -53,6 +55,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|
|
53
55
|
variant = "default",
|
|
54
56
|
size = "default",
|
|
55
57
|
isPending = false,
|
|
58
|
+
contentClassName,
|
|
56
59
|
disabled,
|
|
57
60
|
children,
|
|
58
61
|
className,
|
|
@@ -85,7 +88,8 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|
|
85
88
|
<span
|
|
86
89
|
className={cn(
|
|
87
90
|
"inline-flex items-center justify-center gap-2 transition-opacity duration-200",
|
|
88
|
-
showPendingIndicator && "opacity-0"
|
|
91
|
+
showPendingIndicator && "opacity-0",
|
|
92
|
+
contentClassName
|
|
89
93
|
)}
|
|
90
94
|
>
|
|
91
95
|
{children}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Fragment, type ComponentProps, type ReactNode } from "react"
|
|
2
|
+
import { cn } from "../../lib/class-names"
|
|
3
|
+
|
|
4
|
+
export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children"> {
|
|
5
|
+
code: string
|
|
6
|
+
/** Supply a highlighter's rendered pre/code element; source remains the fallback. */
|
|
7
|
+
children?: ReactNode
|
|
8
|
+
/** Show a nonselectable line gutter. @default true */
|
|
9
|
+
lineNumbers?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Code presentation without shipping a highlighter or formatter to consumers. */
|
|
13
|
+
export function CodeBlock({
|
|
14
|
+
code,
|
|
15
|
+
children,
|
|
16
|
+
lineNumbers = true,
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: CodeBlockProps) {
|
|
20
|
+
const lines = code.split("\n")
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
{...props}
|
|
24
|
+
className={cn("passui-code-block", className)}
|
|
25
|
+
data-line-numbers={lineNumbers || undefined}
|
|
26
|
+
>
|
|
27
|
+
{children ?? (
|
|
28
|
+
<pre>
|
|
29
|
+
<code>
|
|
30
|
+
{lines.map((line, index) => (
|
|
31
|
+
<Fragment key={`${index}-${line}`}>
|
|
32
|
+
<span className="line">{line || " "}</span>
|
|
33
|
+
{index < lines.length - 1 ? "\n" : null}
|
|
34
|
+
</Fragment>
|
|
35
|
+
))}
|
|
36
|
+
</code>
|
|
37
|
+
</pre>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Picker, type PickerOption, type PickerProps } from "../picker/picker"
|
|
2
|
+
|
|
3
|
+
export interface ComboBoxOption<T extends string> extends PickerOption<T> {}
|
|
4
|
+
export interface ComboBoxProps<T extends string>
|
|
5
|
+
extends Omit<PickerProps<T>, "searchable" | "options"> {
|
|
6
|
+
options: readonly ComboBoxOption<T>[]
|
|
7
|
+
emptyLabel: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A searchable Picker, with optional creation from the person's typed query. */
|
|
11
|
+
export function ComboBox<T extends string>(props: ComboBoxProps<T>) {
|
|
12
|
+
return <Picker {...props} searchable />
|
|
13
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type ComponentProps, useEffect, useRef } from "react"
|
|
2
|
+
import { SearchIcon } from "../../icons/icons"
|
|
3
|
+
import { cn } from "../../lib/class-names"
|
|
4
|
+
import { KeyboardShortcut } from "../keyboard-shortcut/keyboard-shortcut"
|
|
5
|
+
import {
|
|
6
|
+
SearchList,
|
|
7
|
+
SearchListField,
|
|
8
|
+
SearchListResults,
|
|
9
|
+
SearchListEmpty,
|
|
10
|
+
SearchListGroup,
|
|
11
|
+
SearchListItem,
|
|
12
|
+
} from "../search-list/search-list"
|
|
13
|
+
import { Sheet, type SheetProps } from "../sheet/sheet"
|
|
14
|
+
|
|
15
|
+
export interface CommandProps extends ComponentProps<typeof SearchList> {}
|
|
16
|
+
export interface CommandDialogProps extends Omit<SheetProps, "placement"> {}
|
|
17
|
+
|
|
18
|
+
/** An inline command palette. SearchList owns filtering and keyboard selection. */
|
|
19
|
+
export function Command({ className, ...props }: CommandProps) {
|
|
20
|
+
return (
|
|
21
|
+
<SearchList
|
|
22
|
+
{...props}
|
|
23
|
+
className={cn(
|
|
24
|
+
"not-typeset glass-menu flex min-h-0 w-full flex-col overflow-hidden rounded-3xl",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A modal command palette with Sheet's focus isolation and restoration. */
|
|
32
|
+
export function CommandDialog({ children, className, ...props }: CommandDialogProps) {
|
|
33
|
+
return (
|
|
34
|
+
<Sheet
|
|
35
|
+
{...props}
|
|
36
|
+
placement="center"
|
|
37
|
+
className={cn("max-w-lg rounded-3xl outline-none", className)}
|
|
38
|
+
>
|
|
39
|
+
<SearchList className="not-typeset flex min-h-0 flex-col">{children}</SearchList>
|
|
40
|
+
</Sheet>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function CommandInput({
|
|
45
|
+
className,
|
|
46
|
+
autoFocus,
|
|
47
|
+
ref: forwardedRef,
|
|
48
|
+
...props
|
|
49
|
+
}: ComponentProps<typeof SearchListField>) {
|
|
50
|
+
const input = useRef<HTMLInputElement>(null)
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (autoFocus) input.current?.focus({ preventScroll: true })
|
|
53
|
+
}, [autoFocus])
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex shrink-0 items-center gap-2 border-b border-separator/50 px-4">
|
|
56
|
+
<SearchIcon size={17} className="shrink-0 text-label-tertiary" />
|
|
57
|
+
<SearchListField
|
|
58
|
+
{...props}
|
|
59
|
+
ref={(node) => {
|
|
60
|
+
input.current = node
|
|
61
|
+
if (typeof forwardedRef === "function") forwardedRef(node)
|
|
62
|
+
else if (forwardedRef) forwardedRef.current = node
|
|
63
|
+
}}
|
|
64
|
+
className={cn(
|
|
65
|
+
"min-h-12 rounded-none border-0 bg-transparent px-0 focus-visible:ring-0",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function CommandList({ className, ...props }: ComponentProps<typeof SearchListResults>) {
|
|
74
|
+
return (
|
|
75
|
+
<SearchListResults {...props} className={cn("max-h-[min(60svh,24rem)] p-1.5", className)} />
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function CommandEmpty({ className, ...props }: ComponentProps<typeof SearchListEmpty>) {
|
|
80
|
+
return (
|
|
81
|
+
<SearchListEmpty
|
|
82
|
+
{...props}
|
|
83
|
+
className={cn("px-3 py-8 text-center text-footnote text-label-secondary", className)}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function CommandShortcut({ className, ...props }: ComponentProps<typeof KeyboardShortcut>) {
|
|
89
|
+
return (
|
|
90
|
+
<KeyboardShortcut
|
|
91
|
+
{...props}
|
|
92
|
+
className={cn("ms-auto border-0 bg-transparent text-label-tertiary", className)}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function CommandGroup({ className, ...props }: ComponentProps<typeof SearchListGroup>) {
|
|
98
|
+
return (
|
|
99
|
+
<SearchListGroup
|
|
100
|
+
{...props}
|
|
101
|
+
className={cn(
|
|
102
|
+
"border-separator/50 not-first:mt-1 not-first:border-t not-first:pt-1",
|
|
103
|
+
className
|
|
104
|
+
)}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function CommandItem({ className, ...props }: ComponentProps<typeof SearchListItem>) {
|
|
110
|
+
return (
|
|
111
|
+
<SearchListItem
|
|
112
|
+
{...props}
|
|
113
|
+
className={cn(
|
|
114
|
+
"[&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-label-secondary",
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
useEffect,
|
|
5
|
-
useId,
|
|
6
|
-
useLayoutEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from "react"
|
|
1
|
+
import { useAnchoredDisclosure } from "../../hooks/use-anchored-disclosure"
|
|
2
|
+
import { AnchoredSurface } from "../popover/anchored-surface"
|
|
3
|
+
import { type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from "react"
|
|
11
4
|
import { createPortal } from "react-dom"
|
|
12
|
-
import { FLOATING_LAYER } from "../../hooks/use-dismiss"
|
|
13
|
-
import { useFloatingDismiss } from "../../hooks/use-floating-dismiss"
|
|
14
5
|
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "../../icons/icons"
|
|
15
6
|
import { cn } from "../../lib/class-names"
|
|
16
|
-
import { registerFloatingLayer } from "../../lib/floating-layers"
|
|
17
7
|
import { haptic } from "../../lib/haptics"
|
|
18
|
-
import { useAnchoredPlacement } from "../../hooks/use-anchored-placement"
|
|
19
|
-
import { floatingPanelMotion } from "../../motion/transitions"
|
|
20
8
|
import { IconButton } from "../button/icon-button"
|
|
21
9
|
import { pickerTriggerStyle } from "../picker/picker-trigger"
|
|
22
10
|
import {
|
|
@@ -82,7 +70,8 @@ export function CompactDatePicker({
|
|
|
82
70
|
className,
|
|
83
71
|
disabled = false,
|
|
84
72
|
}: CompactDatePickerProps) {
|
|
85
|
-
const
|
|
73
|
+
const panel = useAnchoredDisclosure(disabled)
|
|
74
|
+
const { open } = panel
|
|
86
75
|
const trigger = useRef<HTMLButtonElement>(null)
|
|
87
76
|
const calendarId = useId()
|
|
88
77
|
const chosen = parseDay(value)
|
|
@@ -94,7 +83,7 @@ export function CompactDatePicker({
|
|
|
94
83
|
: (placeholder ?? "")
|
|
95
84
|
|
|
96
85
|
const close = (restoreFocus: boolean) => {
|
|
97
|
-
|
|
86
|
+
panel.close()
|
|
98
87
|
if (restoreFocus) trigger.current?.focus({ preventScroll: true })
|
|
99
88
|
}
|
|
100
89
|
|
|
@@ -108,7 +97,7 @@ export function CompactDatePicker({
|
|
|
108
97
|
aria-expanded={open && !disabled}
|
|
109
98
|
aria-controls={open && !disabled ? calendarId : undefined}
|
|
110
99
|
disabled={disabled}
|
|
111
|
-
onClick={() =>
|
|
100
|
+
onClick={() => panel.toggle()}
|
|
112
101
|
className={pickerTriggerStyle(className)}
|
|
113
102
|
>
|
|
114
103
|
<span
|
|
@@ -123,26 +112,26 @@ export function CompactDatePicker({
|
|
|
123
112
|
</button>
|
|
124
113
|
{typeof document !== "undefined" &&
|
|
125
114
|
createPortal(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
115
|
+
panel.mounted && trigger.current ? (
|
|
116
|
+
<MonthCalendar
|
|
117
|
+
open={open}
|
|
118
|
+
onExited={panel.onExited}
|
|
119
|
+
id={calendarId}
|
|
120
|
+
anchor={trigger.current}
|
|
121
|
+
label={label}
|
|
122
|
+
locale={locale}
|
|
123
|
+
value={value}
|
|
124
|
+
min={min}
|
|
125
|
+
max={max}
|
|
126
|
+
labels={labels}
|
|
127
|
+
onChoose={(next) => {
|
|
128
|
+
haptic("selection")
|
|
129
|
+
onChange(next)
|
|
130
|
+
close(true)
|
|
131
|
+
}}
|
|
132
|
+
onClose={close}
|
|
133
|
+
/>
|
|
134
|
+
) : null,
|
|
146
135
|
document.body
|
|
147
136
|
)}
|
|
148
137
|
</>
|
|
@@ -150,6 +139,8 @@ export function CompactDatePicker({
|
|
|
150
139
|
}
|
|
151
140
|
|
|
152
141
|
function MonthCalendar({
|
|
142
|
+
open,
|
|
143
|
+
onExited,
|
|
153
144
|
id,
|
|
154
145
|
anchor,
|
|
155
146
|
label,
|
|
@@ -161,6 +152,8 @@ function MonthCalendar({
|
|
|
161
152
|
onChoose,
|
|
162
153
|
onClose,
|
|
163
154
|
}: {
|
|
155
|
+
open: boolean
|
|
156
|
+
onExited: () => void
|
|
164
157
|
id: string
|
|
165
158
|
anchor: HTMLElement
|
|
166
159
|
label: string
|
|
@@ -173,8 +166,6 @@ function MonthCalendar({
|
|
|
173
166
|
onClose: (restoreFocus: boolean) => void
|
|
174
167
|
}) {
|
|
175
168
|
const root = useRef<HTMLDivElement>(null)
|
|
176
|
-
const reducedMotion = useReducedMotion()
|
|
177
|
-
const placement = useAnchoredPlacement(anchor, WIDTH, 360)
|
|
178
169
|
const today = todayDay()
|
|
179
170
|
const [focused, setFocused] = useState<CalendarDay>(() => parseDay(value) ?? today)
|
|
180
171
|
const firstDay = useMemo(() => firstWeekday(locale), [locale])
|
|
@@ -195,11 +186,6 @@ function MonthCalendar({
|
|
|
195
186
|
timeZone: "UTC",
|
|
196
187
|
})
|
|
197
188
|
|
|
198
|
-
useLayoutEffect(() => {
|
|
199
|
-
if (!root.current) return
|
|
200
|
-
return registerFloatingLayer(root.current, anchor)
|
|
201
|
-
}, [anchor])
|
|
202
|
-
|
|
203
189
|
// The focused day takes keyboard focus as it moves.
|
|
204
190
|
useEffect(() => {
|
|
205
191
|
root.current
|
|
@@ -207,8 +193,6 @@ function MonthCalendar({
|
|
|
207
193
|
?.focus()
|
|
208
194
|
}, [focused])
|
|
209
195
|
|
|
210
|
-
useFloatingDismiss(root, anchor, onClose)
|
|
211
|
-
|
|
212
196
|
const move = (next: CalendarDay) => setFocused(next)
|
|
213
197
|
|
|
214
198
|
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -230,46 +214,18 @@ function MonthCalendar({
|
|
|
230
214
|
}
|
|
231
215
|
|
|
232
216
|
return (
|
|
233
|
-
<
|
|
217
|
+
<AnchoredSurface
|
|
218
|
+
open={open}
|
|
219
|
+
onExited={onExited}
|
|
234
220
|
ref={root}
|
|
221
|
+
anchor={anchor}
|
|
222
|
+
width={WIDTH}
|
|
223
|
+
needed={360}
|
|
224
|
+
onClose={onClose}
|
|
235
225
|
id={id}
|
|
236
226
|
role="dialog"
|
|
237
227
|
aria-label={label}
|
|
238
|
-
|
|
239
|
-
reducedMotion
|
|
240
|
-
? { opacity: 0 }
|
|
241
|
-
: {
|
|
242
|
-
opacity: 0,
|
|
243
|
-
scale: 1,
|
|
244
|
-
y: placement.above ? 6 : -6,
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
248
|
-
exit={
|
|
249
|
-
reducedMotion
|
|
250
|
-
? { opacity: 0 }
|
|
251
|
-
: {
|
|
252
|
-
opacity: 0,
|
|
253
|
-
scale: 1,
|
|
254
|
-
y: placement.above ? 4 : -4,
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
transition={
|
|
258
|
-
reducedMotion
|
|
259
|
-
? { duration: 0 }
|
|
260
|
-
: floatingPanelMotion(placement.above ? "above" : "below").transition
|
|
261
|
-
}
|
|
262
|
-
style={{
|
|
263
|
-
left: placement.left,
|
|
264
|
-
width: placement.width,
|
|
265
|
-
top: placement.top,
|
|
266
|
-
bottom: placement.bottom,
|
|
267
|
-
maxHeight: placement.maxHeight,
|
|
268
|
-
overflowY: placement.maxHeight ? "auto" : undefined,
|
|
269
|
-
transformOrigin: placement.above ? "50% 100%" : "50% 0%",
|
|
270
|
-
}}
|
|
271
|
-
className="glass-menu fixed z-[80] flex flex-col gap-2 rounded-3xl p-3"
|
|
272
|
-
{...{ [FLOATING_LAYER]: "" }}
|
|
228
|
+
contentClassName="gap-2 p-3"
|
|
273
229
|
onKeyDown={onKeyDown}
|
|
274
230
|
>
|
|
275
231
|
<div className="flex items-center justify-between gap-2 ps-2">
|
|
@@ -354,6 +310,6 @@ function MonthCalendar({
|
|
|
354
310
|
{labels.clear}
|
|
355
311
|
</button>
|
|
356
312
|
)}
|
|
357
|
-
</
|
|
313
|
+
</AnchoredSurface>
|
|
358
314
|
)
|
|
359
315
|
}
|