@passui/react 0.1.39 → 0.1.41
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/types/src/components/code-block/code-block.d.ts +5 -2
- package/dist/types/src/components/content-view/content-view.d.ts +5 -2
- package/dist/types/src/components/context-menu/context-menu-store.d.ts +7 -2
- package/dist/types/src/components/conversation/conversation.d.ts +7 -2
- package/dist/types/src/components/one-time-code/one-time-code.d.ts +4 -1
- package/dist/types/src/components/popover/popover.d.ts +3 -0
- package/dist/types/src/components/search-list/search-list.d.ts +4 -1
- package/dist/types/src/components/sidebar/sidebar.d.ts +8 -3
- package/dist/types/src/components/slosh-slider/slosh-slider.d.ts +4 -1
- package/dist/types/src/components/toast/toast-store.d.ts +5 -0
- package/dist/types/src/components/tooltip/tooltip-host.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/code-block/code-block.tsx +7 -1
- package/src/components/content-view/content-view.tsx +60 -8
- package/src/components/context-menu/context-menu-host.tsx +44 -15
- package/src/components/context-menu/context-menu-store.ts +10 -1
- package/src/components/conversation/conversation.tsx +93 -19
- package/src/components/one-time-code/one-time-code.tsx +22 -2
- package/src/components/popover/popover.tsx +15 -2
- package/src/components/search-list/search-list.tsx +27 -9
- package/src/components/sidebar/sidebar.tsx +38 -11
- package/src/components/slosh-slider/slosh-slider.tsx +6 -0
- package/src/components/toast/toast-store.ts +7 -0
- package/src/components/toast/toaster.tsx +56 -23
- package/src/components/tooltip/tooltip-host.tsx +14 -1
- package/src/styles/code-block.css +12 -0
- package/src/styles/motion-controls.css +8 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
-
export
|
|
2
|
+
export type CodeBlockSize = "sm" | "md" | "lg";
|
|
3
|
+
export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children" | "size"> {
|
|
3
4
|
code: string;
|
|
4
5
|
/** Supply a highlighter's rendered pre/code element; source remains the fallback. */
|
|
5
6
|
children?: ReactNode;
|
|
6
7
|
/** Show a nonselectable line gutter. @default true */
|
|
7
8
|
lineNumbers?: boolean;
|
|
9
|
+
/** Code font size, padding and line gutter scale. @default "md" */
|
|
10
|
+
size?: CodeBlockSize;
|
|
8
11
|
}
|
|
9
12
|
/** Code presentation without shipping a highlighter or formatter to consumers. */
|
|
10
|
-
export declare function CodeBlock({ code, children, lineNumbers, className, ...props }: CodeBlockProps): import("react").JSX.Element;
|
|
13
|
+
export declare function CodeBlock({ code, children, lineNumbers, size, className, ...props }: CodeBlockProps): import("react").JSX.Element;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithoutRef, type PropsWithChildren, type ReactNode } from "react";
|
|
2
2
|
export type ContentViewVariant = keyof typeof variants;
|
|
3
3
|
export type ContentViewHeadingLevel = "h1" | "h2" | "h3";
|
|
4
|
+
export type ContentViewSize = "sm" | "md" | "lg";
|
|
4
5
|
type ContentViewProps = ComponentPropsWithoutRef<"section"> & {
|
|
5
6
|
variant?: ContentViewVariant;
|
|
7
|
+
/** Container radius, section gap and text scale. @default "md" */
|
|
8
|
+
size?: ContentViewSize;
|
|
6
9
|
};
|
|
7
10
|
type RowProps = ComponentPropsWithoutRef<"div">;
|
|
8
11
|
type SectionProps = ComponentPropsWithoutRef<"section">;
|
|
@@ -24,7 +27,7 @@ type SubProps = ComponentPropsWithoutRef<"sub">;
|
|
|
24
27
|
declare const variants: {
|
|
25
28
|
readonly elevated: string;
|
|
26
29
|
};
|
|
27
|
-
export declare function ContentView({ variant, className, children, ...props }: ContentViewProps): import("react").JSX.Element;
|
|
30
|
+
export declare function ContentView({ variant, size, className, children, ...props }: ContentViewProps): import("react").JSX.Element;
|
|
28
31
|
export declare namespace ContentView {
|
|
29
32
|
export { Row };
|
|
30
33
|
export { Section };
|
|
@@ -3,6 +3,7 @@ import type { MenuRect } from "./menu-reveal";
|
|
|
3
3
|
export type ContextMenuAlignX = "start" | "end";
|
|
4
4
|
export type ContextMenuAlignY = "top" | "bottom";
|
|
5
5
|
export type ContextMenuEdge = "leading" | "trailing";
|
|
6
|
+
export type ContextMenuSize = "sm" | "md" | "lg";
|
|
6
7
|
/** One row of a context menu, or a divider between groups of rows. */
|
|
7
8
|
export type ContextMenuItem = {
|
|
8
9
|
label: string;
|
|
@@ -23,6 +24,8 @@ export interface OpenMenu {
|
|
|
23
24
|
items: ContextMenuItem[];
|
|
24
25
|
/** Opened by long-press: bigger rows for fingers. */
|
|
25
26
|
touch: boolean;
|
|
27
|
+
/** Surface, item and heading density. @default "md" */
|
|
28
|
+
size: ContextMenuSize;
|
|
26
29
|
/** Which edge of the menu sits at `x`: its left (`start`) or its right (`end`). */
|
|
27
30
|
alignX: ContextMenuAlignX;
|
|
28
31
|
/** Which edge of the menu sits at `y`: its top (`top`) or its bottom (`bottom`). */
|
|
@@ -33,7 +36,7 @@ export interface OpenMenu {
|
|
|
33
36
|
restoreFocus?: boolean;
|
|
34
37
|
}
|
|
35
38
|
/** Opens a menu at a point, such as where a pointer was pressed. */
|
|
36
|
-
export declare function openContextMenu(x: number, y: number, items: ContextMenuItem[], touch?: boolean, anchor?: Element | null): void;
|
|
39
|
+
export declare function openContextMenu(x: number, y: number, items: ContextMenuItem[], touch?: boolean, anchor?: Element | null, size?: ContextMenuSize): void;
|
|
37
40
|
/**
|
|
38
41
|
* Opens a second menu where the last one stood, the way an iOS menu swaps
|
|
39
42
|
* in a submenu: a long list (a note's fonts, its kinds) stays one row in the
|
|
@@ -46,11 +49,13 @@ export declare function openFollowUpMenu(items: ContextMenuItem[]): void;
|
|
|
46
49
|
* trailing follow the text direction, so right-to-left languages mirror.
|
|
47
50
|
* The menu's real size decides where it goes; nothing is guessed.
|
|
48
51
|
*/
|
|
49
|
-
export declare function openMenu(anchor: Element, items: ContextMenuItem[], { edge, above, toolbar, }?: {
|
|
52
|
+
export declare function openMenu(anchor: Element, items: ContextMenuItem[], { edge, above, toolbar, size, }?: {
|
|
50
53
|
edge?: ContextMenuEdge;
|
|
51
54
|
above?: boolean;
|
|
52
55
|
/** Replace this action group with a horizontally expanding menu surface. */
|
|
53
56
|
toolbar?: HTMLElement;
|
|
57
|
+
/** Surface, item and heading density. @default "md" */
|
|
58
|
+
size?: ContextMenuSize;
|
|
54
59
|
}): void;
|
|
55
60
|
export declare function closeContextMenu(restoreFocus?: boolean): void;
|
|
56
61
|
export declare function subscribeContextMenu(listener: () => void): () => void;
|
|
@@ -9,15 +9,18 @@ export type ConversationMessage = {
|
|
|
9
9
|
avatar?: ReactNode;
|
|
10
10
|
createdAt: string;
|
|
11
11
|
};
|
|
12
|
+
export type ConversationSize = "sm" | "md" | "lg";
|
|
12
13
|
/**
|
|
13
14
|
* A thread of plain-text messages like Messages on iPhone: a day line when
|
|
14
15
|
* the date changes, back-to-back messages from one side grouped under one
|
|
15
16
|
* name, theirs on the left with an avatar, yours on the right.
|
|
16
17
|
*/
|
|
17
|
-
declare function Messages({ messages, locale, }: {
|
|
18
|
+
declare function Messages({ messages, locale, size, }: {
|
|
18
19
|
messages: ConversationMessage[];
|
|
19
20
|
/** For day names and times; the reader's own by default. */
|
|
20
21
|
locale?: string;
|
|
22
|
+
/** Bubble, avatar, author and time scale. @default "md" */
|
|
23
|
+
size?: ConversationSize;
|
|
21
24
|
}): import("react").JSX.Element;
|
|
22
25
|
type ComposerProps = {
|
|
23
26
|
onSend: (body: string) => Promise<unknown>;
|
|
@@ -33,9 +36,11 @@ type ComposerProps = {
|
|
|
33
36
|
* (count) => `${count} left`. Without it, just the number shows.
|
|
34
37
|
*/
|
|
35
38
|
remaining?: (count: number) => string;
|
|
39
|
+
/** Surface, textarea, send button and counter scale. @default "md" */
|
|
40
|
+
size?: ConversationSize;
|
|
36
41
|
};
|
|
37
42
|
/** A reply field with a round send button; Ctrl/Cmd+Enter sends, and it clears only once the send worked. */
|
|
38
|
-
declare function Composer({ onSend, pending, maxLength, placeholder, actions, sendLabel, remaining, }: ComposerProps): import("react").JSX.Element;
|
|
43
|
+
declare function Composer({ onSend, pending, maxLength, placeholder, actions, sendLabel, remaining, size, }: ComposerProps): import("react").JSX.Element;
|
|
39
44
|
export declare const Conversation: {
|
|
40
45
|
Messages: typeof Messages;
|
|
41
46
|
Composer: typeof Composer;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type InputHTMLAttributes, type Ref } from "react";
|
|
2
2
|
export type OneTimeCodeStatus = "idle" | "verifying" | "success" | "error";
|
|
3
3
|
export type OneTimeCodeFormat = "numeric" | "alphanumeric";
|
|
4
|
+
export type OneTimeCodeSize = "sm" | "md" | "lg";
|
|
4
5
|
export interface OneTimeCodeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "maxLength" | "size"> {
|
|
5
6
|
label: string;
|
|
6
7
|
value: string;
|
|
@@ -16,8 +17,10 @@ export interface OneTimeCodeProps extends Omit<InputHTMLAttributes<HTMLInputElem
|
|
|
16
17
|
status?: OneTimeCodeStatus;
|
|
17
18
|
/** Localized verification feedback announced without reading decorative cells. */
|
|
18
19
|
statusLabel?: string;
|
|
20
|
+
/** Cell height, digit text scale and check icon size. @default "md" */
|
|
21
|
+
size?: OneTimeCodeSize;
|
|
19
22
|
}
|
|
20
23
|
/** Normalize common numeric keyboards and optional ASCII letters in one native field. */
|
|
21
24
|
export declare function normalizeCode(value: string, length: number, format?: OneTimeCodeFormat): string;
|
|
22
25
|
/** Six cells, one field: paste, autofill, selection and Backspace stay native. */
|
|
23
|
-
export declare function OneTimeCode({ label, value, onChange, onComplete, length, format, ref, status, statusLabel, className, disabled, id: suppliedId, ...props }: OneTimeCodeProps): import("react").JSX.Element;
|
|
26
|
+
export declare function OneTimeCode({ label, value, onChange, onComplete, length, format, ref, status, statusLabel, size, className, disabled, id: suppliedId, ...props }: OneTimeCodeProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type HTMLMotionProps } from "motion/react";
|
|
2
2
|
import { type ReactNode, type RefObject } from "react";
|
|
3
3
|
export type PopoverOrigin = "top-left" | "top-right";
|
|
4
|
+
export type PopoverSize = "sm" | "md";
|
|
4
5
|
export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
|
|
5
6
|
open: boolean;
|
|
6
7
|
children?: ReactNode;
|
|
@@ -8,6 +9,8 @@ export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
|
|
|
8
9
|
origin?: PopoverOrigin;
|
|
9
10
|
/** Trigger whose shape the popover uses during its opening and closing motion. */
|
|
10
11
|
anchorRef?: RefObject<HTMLElement | null>;
|
|
12
|
+
/** Surface corner radius. @default "md" */
|
|
13
|
+
size?: PopoverSize;
|
|
11
14
|
}
|
|
12
15
|
/**
|
|
13
16
|
* A glass surface that springs open from its anchor. Position it with
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type ComponentProps } from "react";
|
|
2
|
-
export
|
|
2
|
+
export type SearchListSize = "sm" | "md" | "lg";
|
|
3
|
+
export declare function SearchList({ loop, open, size, onKeyDown, className, ...props }: ComponentProps<"div"> & {
|
|
3
4
|
loop?: boolean;
|
|
4
5
|
open?: boolean;
|
|
6
|
+
/** Field, item and heading density. @default "md" */
|
|
7
|
+
size?: SearchListSize;
|
|
5
8
|
}): import("react").JSX.Element;
|
|
6
9
|
export declare function SearchListField({ value, onValueChange, className, ...props }: Omit<ComponentProps<"input">, "value" | "onChange"> & {
|
|
7
10
|
value?: string;
|
|
@@ -12,8 +12,13 @@ export interface SidebarItemContentProps {
|
|
|
12
12
|
active?: boolean;
|
|
13
13
|
children: ReactNode;
|
|
14
14
|
}
|
|
15
|
-
export declare function SidebarSection({ title, children }: SidebarSectionProps
|
|
15
|
+
export declare function SidebarSection({ title, children, size, }: SidebarSectionProps & {
|
|
16
|
+
size?: SidebarSize;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
16
18
|
/** Classes for a sidebar row; apply to the app's router link. */
|
|
17
19
|
export type SidebarDensity = "regular" | "compact";
|
|
18
|
-
export
|
|
19
|
-
export declare function
|
|
20
|
+
export type SidebarSize = "sm" | "md" | "lg";
|
|
21
|
+
export declare function sidebarItemClass(active?: boolean, density?: SidebarDensity, size?: SidebarSize): string;
|
|
22
|
+
export declare function SidebarItemContent({ icon, count, active, size, children, }: SidebarItemContentProps & {
|
|
23
|
+
size?: SidebarSize;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type SloshSliderSize = "sm" | "md" | "lg";
|
|
1
2
|
export interface SloshSliderProps {
|
|
2
3
|
label: string;
|
|
3
4
|
value?: number;
|
|
@@ -11,8 +12,10 @@ export interface SloshSliderProps {
|
|
|
11
12
|
tilt?: number;
|
|
12
13
|
/** Track corner, from square to a pill, 0–20. @default 20 */
|
|
13
14
|
corner?: number;
|
|
15
|
+
/** Track height and knob scale. @default "md" */
|
|
16
|
+
size?: SloshSliderSize;
|
|
14
17
|
disabled?: boolean;
|
|
15
18
|
className?: string;
|
|
16
19
|
formatValue?: (value: number) => string;
|
|
17
20
|
}
|
|
18
|
-
export declare function SloshSlider({ label, value, defaultValue, onChange, viscosity, momentum, tilt, corner, disabled, className, formatValue, }: SloshSliderProps): import("react").JSX.Element;
|
|
21
|
+
export declare function SloshSlider({ label, value, defaultValue, onChange, viscosity, momentum, tilt, corner, size, disabled, className, formatValue, }: SloshSliderProps): import("react").JSX.Element;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* a failure. Call `toast` from anywhere; render one `<Toaster />`.
|
|
4
4
|
*/
|
|
5
5
|
export type ToastTone = "neutral" | "success" | "error" | "loading";
|
|
6
|
+
export type ToastSize = "sm" | "md" | "lg";
|
|
6
7
|
export interface ToastAction {
|
|
7
8
|
label: string;
|
|
8
9
|
onClick: () => void;
|
|
@@ -16,11 +17,15 @@ export interface Toast {
|
|
|
16
17
|
/** Milliseconds before it leaves on its own; `null` stays until dismissed. */
|
|
17
18
|
duration: number | null;
|
|
18
19
|
createdAt: number;
|
|
20
|
+
/** Pill size, content padding and title/description text scale. @default "md" */
|
|
21
|
+
size: ToastSize;
|
|
19
22
|
}
|
|
20
23
|
export interface ToastOptions {
|
|
21
24
|
description?: string;
|
|
22
25
|
action?: ToastAction;
|
|
23
26
|
duration?: number | null;
|
|
27
|
+
/** Pill size, content padding and title/description text scale. @default "md" */
|
|
28
|
+
size?: ToastSize;
|
|
24
29
|
}
|
|
25
30
|
/** Changes a toast in place; it morphs to its new state. */
|
|
26
31
|
declare function update(id: number, tone: ToastTone, title: string, options?: ToastOptions): number;
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { Fragment, type ComponentProps, type ReactNode } from "react"
|
|
2
2
|
import { cn } from "../../lib/class-names"
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export type CodeBlockSize = "sm" | "md" | "lg"
|
|
5
|
+
|
|
6
|
+
export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children" | "size"> {
|
|
5
7
|
code: string
|
|
6
8
|
/** Supply a highlighter's rendered pre/code element; source remains the fallback. */
|
|
7
9
|
children?: ReactNode
|
|
8
10
|
/** Show a nonselectable line gutter. @default true */
|
|
9
11
|
lineNumbers?: boolean
|
|
12
|
+
/** Code font size, padding and line gutter scale. @default "md" */
|
|
13
|
+
size?: CodeBlockSize
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
/** Code presentation without shipping a highlighter or formatter to consumers. */
|
|
@@ -14,6 +18,7 @@ export function CodeBlock({
|
|
|
14
18
|
code,
|
|
15
19
|
children,
|
|
16
20
|
lineNumbers = true,
|
|
21
|
+
size = "md",
|
|
17
22
|
className,
|
|
18
23
|
...props
|
|
19
24
|
}: CodeBlockProps) {
|
|
@@ -23,6 +28,7 @@ export function CodeBlock({
|
|
|
23
28
|
{...props}
|
|
24
29
|
className={cn("passui-code-block", className)}
|
|
25
30
|
data-line-numbers={lineNumbers || undefined}
|
|
31
|
+
data-size={size}
|
|
26
32
|
>
|
|
27
33
|
{children ?? (
|
|
28
34
|
<pre>
|
|
@@ -1,11 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
type ComponentPropsWithoutRef,
|
|
5
|
+
type PropsWithChildren,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react"
|
|
2
8
|
import { cn } from "../../lib/class-names"
|
|
3
9
|
|
|
4
10
|
export type ContentViewVariant = keyof typeof variants
|
|
5
11
|
export type ContentViewHeadingLevel = "h1" | "h2" | "h3"
|
|
12
|
+
export type ContentViewSize = "sm" | "md" | "lg"
|
|
13
|
+
|
|
14
|
+
const containerRadius: Record<ContentViewSize, string> = {
|
|
15
|
+
sm: "rounded-xl",
|
|
16
|
+
md: "rounded-2xl",
|
|
17
|
+
lg: "rounded-3xl",
|
|
18
|
+
}
|
|
19
|
+
const sectionGaps: Record<ContentViewSize, string> = {
|
|
20
|
+
sm: "gap-2",
|
|
21
|
+
md: "gap-3",
|
|
22
|
+
lg: "gap-4",
|
|
23
|
+
}
|
|
24
|
+
const spanText: Record<ContentViewSize, string> = {
|
|
25
|
+
sm: "text-footnote tracking-tighter font-light",
|
|
26
|
+
md: "text-callout tracking-tighter font-light",
|
|
27
|
+
lg: "text-subheadline tracking-tighter font-light",
|
|
28
|
+
}
|
|
29
|
+
const subText: Record<ContentViewSize, string> = {
|
|
30
|
+
sm: "text-footnote text-label",
|
|
31
|
+
md: "text-subheadline text-label",
|
|
32
|
+
lg: "text-body text-label",
|
|
33
|
+
}
|
|
34
|
+
const headerActionGaps: Record<ContentViewSize, string> = {
|
|
35
|
+
sm: "gap-2",
|
|
36
|
+
md: "gap-3",
|
|
37
|
+
lg: "gap-4",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ContentViewSizeContext = createContext<ContentViewSize>("md")
|
|
41
|
+
const useContentViewSize = () => useContext(ContentViewSizeContext)
|
|
6
42
|
|
|
7
43
|
type ContentViewProps = ComponentPropsWithoutRef<"section"> & {
|
|
8
44
|
variant?: ContentViewVariant
|
|
45
|
+
/** Container radius, section gap and text scale. @default "md" */
|
|
46
|
+
size?: ContentViewSize
|
|
9
47
|
}
|
|
10
48
|
|
|
11
49
|
type RowProps = ComponentPropsWithoutRef<"div">
|
|
@@ -38,14 +76,24 @@ const variants = {
|
|
|
38
76
|
|
|
39
77
|
export function ContentView({
|
|
40
78
|
variant = "elevated",
|
|
79
|
+
size = "md",
|
|
41
80
|
className,
|
|
42
81
|
children,
|
|
43
82
|
...props
|
|
44
83
|
}: ContentViewProps) {
|
|
45
84
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
<ContentViewSizeContext value={size}>
|
|
86
|
+
<section
|
|
87
|
+
className={cn(
|
|
88
|
+
variants[variant],
|
|
89
|
+
variant === "elevated" && containerRadius[size],
|
|
90
|
+
className
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</section>
|
|
96
|
+
</ContentViewSizeContext>
|
|
49
97
|
)
|
|
50
98
|
}
|
|
51
99
|
|
|
@@ -58,8 +106,9 @@ function Row({ className, children, ...props }: RowProps) {
|
|
|
58
106
|
}
|
|
59
107
|
|
|
60
108
|
function Section({ className, children, ...props }: SectionProps) {
|
|
109
|
+
const size = useContentViewSize()
|
|
61
110
|
return (
|
|
62
|
-
<section className={cn("flex flex-col
|
|
111
|
+
<section className={cn("flex flex-col", sectionGaps[size], className)} {...props}>
|
|
63
112
|
{children}
|
|
64
113
|
</section>
|
|
65
114
|
)
|
|
@@ -83,10 +132,11 @@ function Header({
|
|
|
83
132
|
</Tag>
|
|
84
133
|
)
|
|
85
134
|
|
|
135
|
+
const size = useContentViewSize()
|
|
86
136
|
return (
|
|
87
137
|
<div className={className}>
|
|
88
138
|
{action ? (
|
|
89
|
-
<div className={cn("flex items-center
|
|
139
|
+
<div className={cn("flex items-center", headerActionGaps[size], actionClassName)}>
|
|
90
140
|
{title}
|
|
91
141
|
{action}
|
|
92
142
|
</div>
|
|
@@ -128,16 +178,18 @@ function P({ className, children, ...props }: PProps) {
|
|
|
128
178
|
}
|
|
129
179
|
|
|
130
180
|
function Span({ className, children, ...props }: SpanProps) {
|
|
181
|
+
const size = useContentViewSize()
|
|
131
182
|
return (
|
|
132
|
-
<span className={cn(
|
|
183
|
+
<span className={cn(spanText[size], className)} {...props}>
|
|
133
184
|
{children}
|
|
134
185
|
</span>
|
|
135
186
|
)
|
|
136
187
|
}
|
|
137
188
|
|
|
138
189
|
function Sub({ className, children, ...props }: SubProps) {
|
|
190
|
+
const size = useContentViewSize()
|
|
139
191
|
return (
|
|
140
|
-
<sub className={cn(
|
|
192
|
+
<sub className={cn(subText[size], className)} {...props}>
|
|
141
193
|
{children}
|
|
142
194
|
</sub>
|
|
143
195
|
)
|
|
@@ -13,9 +13,45 @@ import { menuSurfaceExit, menuSurfaceReveal } from "./menu-reveal"
|
|
|
13
13
|
import {
|
|
14
14
|
closeContextMenu,
|
|
15
15
|
getContextMenu,
|
|
16
|
-
type
|
|
16
|
+
type ContextMenuItem,
|
|
17
|
+
type ContextMenuSize,
|
|
18
|
+
openContextMenu,
|
|
19
|
+
openFollowUpMenu,
|
|
20
|
+
openMenu,
|
|
17
21
|
subscribeContextMenu,
|
|
18
22
|
} from "./context-menu-store"
|
|
23
|
+
import type { OpenMenu } from "./context-menu-store"
|
|
24
|
+
|
|
25
|
+
const surfaceSizes: Record<ContextMenuSize, string> = {
|
|
26
|
+
sm: "min-w-44 max-w-[min(240px,calc(100vw-24px))] rounded-2xl p-1",
|
|
27
|
+
md: "min-w-50 max-w-[min(280px,calc(100vw-24px))] rounded-3xl p-1.5",
|
|
28
|
+
lg: "min-w-56 max-w-[min(320px,calc(100vw-24px))] rounded-3xl p-2",
|
|
29
|
+
}
|
|
30
|
+
const itemSizes: Record<ContextMenuSize, string> = {
|
|
31
|
+
sm: "flex items-center gap-2 rounded-md px-2 py-1 text-start text-caption transition-colors disabled:opacity-40",
|
|
32
|
+
md: "flex items-center gap-2.5 rounded-lg px-2.5 text-start text-subheadline transition-colors disabled:opacity-40",
|
|
33
|
+
lg: "flex items-center gap-3 rounded-lg px-3 py-2 text-start text-body transition-colors disabled:opacity-40",
|
|
34
|
+
}
|
|
35
|
+
const headingSizes: Record<ContextMenuSize, string> = {
|
|
36
|
+
sm: "px-2 pt-1 pb-0.5 text-caption2 font-semibold text-label-tertiary",
|
|
37
|
+
md: "px-2.5 pt-1.5 pb-0.5 text-caption2 font-semibold text-label-tertiary",
|
|
38
|
+
lg: "px-3 pt-2 pb-1 text-caption font-semibold text-label-tertiary",
|
|
39
|
+
}
|
|
40
|
+
const dividerSizes: Record<ContextMenuSize, string> = {
|
|
41
|
+
sm: "mx-1.5 my-0.5 h-px bg-separator/80",
|
|
42
|
+
md: "mx-2 my-1 h-px bg-separator/80",
|
|
43
|
+
lg: "mx-2.5 my-1.5 h-px bg-separator/80",
|
|
44
|
+
}
|
|
45
|
+
const iconSlotSizes: Record<ContextMenuSize, string> = {
|
|
46
|
+
sm: "flex w-3.5 shrink-0 justify-center text-label-secondary",
|
|
47
|
+
md: "flex w-4 shrink-0 justify-center text-label-secondary",
|
|
48
|
+
lg: "flex w-5 shrink-0 justify-center text-label-secondary",
|
|
49
|
+
}
|
|
50
|
+
const checkIconSizes: Record<ContextMenuSize, number> = {
|
|
51
|
+
sm: 13,
|
|
52
|
+
md: 15,
|
|
53
|
+
lg: 17,
|
|
54
|
+
}
|
|
19
55
|
|
|
20
56
|
/** Renders the open context menu. Mount once, near the app root. */
|
|
21
57
|
export function ContextMenuHost() {
|
|
@@ -251,7 +287,8 @@ function Menu({ menu }: { menu: OpenMenu }) {
|
|
|
251
287
|
}}
|
|
252
288
|
{...{ [FLOATING_LAYER]: "" }}
|
|
253
289
|
className={cn(
|
|
254
|
-
"fixed z-75 isolate flex
|
|
290
|
+
"fixed z-75 isolate flex flex-col outline-none",
|
|
291
|
+
surfaceSizes[menu.size],
|
|
255
292
|
menu.touch && "min-w-60",
|
|
256
293
|
!present && "pointer-events-none"
|
|
257
294
|
)}
|
|
@@ -327,19 +364,11 @@ function Menu({ menu }: { menu: OpenMenu }) {
|
|
|
327
364
|
>
|
|
328
365
|
{menu.items.map((item, index) => {
|
|
329
366
|
if (item === "divider") {
|
|
330
|
-
return
|
|
331
|
-
<div
|
|
332
|
-
key={`divider-${index}`}
|
|
333
|
-
className="mx-2 my-1 h-px bg-separator/80"
|
|
334
|
-
/>
|
|
335
|
-
)
|
|
367
|
+
return <div key={`divider-${index}`} className={dividerSizes[menu.size]} />
|
|
336
368
|
}
|
|
337
369
|
if ("heading" in item) {
|
|
338
370
|
return (
|
|
339
|
-
<span
|
|
340
|
-
key={`heading-${index}`}
|
|
341
|
-
className="px-2.5 pt-1.5 pb-0.5 text-caption2 font-semibold text-label-tertiary"
|
|
342
|
-
>
|
|
371
|
+
<span key={`heading-${index}`} className={headingSizes[menu.size]}>
|
|
343
372
|
{item.heading}
|
|
344
373
|
</span>
|
|
345
374
|
)
|
|
@@ -360,7 +389,7 @@ function Menu({ menu }: { menu: OpenMenu }) {
|
|
|
360
389
|
item.onSelect()
|
|
361
390
|
}}
|
|
362
391
|
className={cn(
|
|
363
|
-
|
|
392
|
+
itemSizes[menu.size],
|
|
364
393
|
menu.touch ? "py-2.5" : "py-1.5",
|
|
365
394
|
item.destructive ? "text-danger" : "text-label",
|
|
366
395
|
focused === current &&
|
|
@@ -368,10 +397,10 @@ function Menu({ menu }: { menu: OpenMenu }) {
|
|
|
368
397
|
(item.destructive ? "bg-danger/10" : "bg-fill")
|
|
369
398
|
)}
|
|
370
399
|
>
|
|
371
|
-
<span className=
|
|
400
|
+
<span className={iconSlotSizes[menu.size]}>
|
|
372
401
|
{item.checked ? (
|
|
373
402
|
<CheckIcon
|
|
374
|
-
size={
|
|
403
|
+
size={checkIconSizes[menu.size]}
|
|
375
404
|
strokeWidth={2.4}
|
|
376
405
|
className="text-accent-text"
|
|
377
406
|
/>
|
|
@@ -4,6 +4,7 @@ import type { MenuRect } from "./menu-reveal"
|
|
|
4
4
|
export type ContextMenuAlignX = "start" | "end"
|
|
5
5
|
export type ContextMenuAlignY = "top" | "bottom"
|
|
6
6
|
export type ContextMenuEdge = "leading" | "trailing"
|
|
7
|
+
export type ContextMenuSize = "sm" | "md" | "lg"
|
|
7
8
|
|
|
8
9
|
/** One row of a context menu, or a divider between groups of rows. */
|
|
9
10
|
export type ContextMenuItem =
|
|
@@ -27,6 +28,8 @@ export interface OpenMenu {
|
|
|
27
28
|
items: ContextMenuItem[]
|
|
28
29
|
/** Opened by long-press: bigger rows for fingers. */
|
|
29
30
|
touch: boolean
|
|
31
|
+
/** Surface, item and heading density. @default "md" */
|
|
32
|
+
size: ContextMenuSize
|
|
30
33
|
/** Which edge of the menu sits at `x`: its left (`start`) or its right (`end`). */
|
|
31
34
|
alignX: ContextMenuAlignX
|
|
32
35
|
/** Which edge of the menu sits at `y`: its top (`top`) or its bottom (`bottom`). */
|
|
@@ -52,7 +55,8 @@ export function openContextMenu(
|
|
|
52
55
|
y: number,
|
|
53
56
|
items: ContextMenuItem[],
|
|
54
57
|
touch = false,
|
|
55
|
-
anchor: Element | null = typeof document === "undefined" ? null : document.activeElement
|
|
58
|
+
anchor: Element | null = typeof document === "undefined" ? null : document.activeElement,
|
|
59
|
+
size: ContextMenuSize = "md"
|
|
56
60
|
) {
|
|
57
61
|
menu = {
|
|
58
62
|
id: nextId++,
|
|
@@ -61,6 +65,7 @@ export function openContextMenu(
|
|
|
61
65
|
y,
|
|
62
66
|
items,
|
|
63
67
|
touch,
|
|
68
|
+
size,
|
|
64
69
|
alignX: "start",
|
|
65
70
|
alignY: "top",
|
|
66
71
|
}
|
|
@@ -93,11 +98,14 @@ export function openMenu(
|
|
|
93
98
|
edge = "leading",
|
|
94
99
|
above = false,
|
|
95
100
|
toolbar,
|
|
101
|
+
size = "md",
|
|
96
102
|
}: {
|
|
97
103
|
edge?: ContextMenuEdge
|
|
98
104
|
above?: boolean
|
|
99
105
|
/** Replace this action group with a horizontally expanding menu surface. */
|
|
100
106
|
toolbar?: HTMLElement
|
|
107
|
+
/** Surface, item and heading density. @default "md" */
|
|
108
|
+
size?: ContextMenuSize
|
|
101
109
|
} = {}
|
|
102
110
|
) {
|
|
103
111
|
const rect = anchor.getBoundingClientRect()
|
|
@@ -112,6 +120,7 @@ export function openMenu(
|
|
|
112
120
|
y: toolbar ? rect.top : above ? rect.bottom : rect.top,
|
|
113
121
|
items,
|
|
114
122
|
touch: false,
|
|
123
|
+
size,
|
|
115
124
|
alignX: right ? "end" : "start",
|
|
116
125
|
alignY: above ? "bottom" : "top",
|
|
117
126
|
toolbar,
|