@passui/react 0.1.35 → 0.1.37

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.
Files changed (33) hide show
  1. package/dist/types/src/components/accordion/accordion.d.ts +5 -1
  2. package/dist/types/src/components/date-picker/date-picker.d.ts +4 -1
  3. package/dist/types/src/components/dialog/dialog.d.ts +4 -1
  4. package/dist/types/src/components/disclosure-group/disclosure-group.d.ts +4 -1
  5. package/dist/types/src/components/morph/index.d.ts +2 -0
  6. package/dist/types/src/components/morph/morph.d.ts +29 -0
  7. package/dist/types/src/components/morph/use-morph-disclosure.d.ts +13 -0
  8. package/dist/types/src/components/radio-group/radio-group.d.ts +4 -1
  9. package/dist/types/src/components/segmented-control/segmented-control.d.ts +4 -1
  10. package/dist/types/src/components/sheet/sheet.d.ts +3 -0
  11. package/dist/types/src/components/slider/slider.d.ts +5 -2
  12. package/dist/types/src/components/stepper/stepper.d.ts +4 -1
  13. package/dist/types/src/components/swatch-picker/swatch-picker.d.ts +4 -1
  14. package/dist/types/src/components/tab-view/tab-view.d.ts +4 -1
  15. package/dist/types/src/components/toggle/toggle.d.ts +4 -1
  16. package/dist/types/src/index.d.ts +1 -0
  17. package/package.json +5 -1
  18. package/src/components/accordion/accordion.tsx +16 -3
  19. package/src/components/date-picker/date-picker.tsx +37 -6
  20. package/src/components/dialog/dialog.tsx +48 -5
  21. package/src/components/disclosure-group/disclosure-group.tsx +31 -9
  22. package/src/components/morph/index.ts +2 -0
  23. package/src/components/morph/morph.tsx +210 -0
  24. package/src/components/morph/use-morph-disclosure.ts +27 -0
  25. package/src/components/radio-group/radio-group.tsx +30 -10
  26. package/src/components/segmented-control/segmented-control.tsx +47 -6
  27. package/src/components/sheet/sheet.tsx +20 -2
  28. package/src/components/slider/slider.tsx +58 -9
  29. package/src/components/stepper/stepper.tsx +37 -11
  30. package/src/components/swatch-picker/swatch-picker.tsx +40 -5
  31. package/src/components/tab-view/tab-view.tsx +31 -4
  32. package/src/components/toggle/toggle.tsx +105 -20
  33. package/src/index.ts +2 -0
@@ -1,4 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
+ import { type DisclosureGroupSize } from "../disclosure-group/disclosure-group";
3
+ export type AccordionSize = DisclosureGroupSize;
2
4
  export interface AccordionItem {
3
5
  /** Stable value used to identify the expanded item. */
4
6
  value: string;
@@ -13,7 +15,9 @@ export interface AccordionProps {
13
15
  value: string | null;
14
16
  /** Selecting the open item reports `null`, so every item can collapse. */
15
17
  onValueChange: (value: string | null) => void;
18
+ /** Density passed through to each DisclosureGroup. @default "md" */
19
+ size?: AccordionSize;
16
20
  className?: string;
17
21
  }
18
22
  /** A single-open, collapsible accordion with labelled animated content regions. */
19
- export declare function Accordion({ items, value, onValueChange, className }: AccordionProps): import("react").JSX.Element;
23
+ export declare function Accordion({ items, value, onValueChange, size, className }: AccordionProps): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ import { type TextFieldSize } from "../text-field/text-field";
1
2
  type DatePickerProps = {
2
3
  value: string;
3
4
  onChange: (value: string) => void;
@@ -10,7 +11,9 @@ type DatePickerProps = {
10
11
  };
11
12
  locale: string;
12
13
  max?: string;
14
+ /** Field scale passed to each TextField; matches TextField's xs|sm|md|lg. @default "md" */
15
+ size?: TextFieldSize;
13
16
  };
14
17
  /** Segmented date entry suited to birthdays, without a native calendar dialog. */
15
- export declare function DatePicker({ value, onChange, label, labels, locale, max }: DatePickerProps): import("react").JSX.Element;
18
+ export declare function DatePicker({ value, onChange, label, labels, locale, max, size, }: DatePickerProps): import("react").JSX.Element;
16
19
  export {};
@@ -1,6 +1,7 @@
1
1
  import { type FormEvent, type ReactNode } from "react";
2
2
  export type DialogPresentation = "drawer" | "center" | "inline";
3
3
  export type DialogTone = "default" | "destructive";
4
+ export type DialogSize = "sm" | "md" | "lg";
4
5
  export type DialogProps = {
5
6
  open: boolean;
6
7
  onOpenChange: (open: boolean) => void;
@@ -10,6 +11,8 @@ export type DialogProps = {
10
11
  footer?: ReactNode;
11
12
  /** `drawer` (responsive sheet), `center` (floating panel), or `inline`. @default "drawer" */
12
13
  presentation?: DialogPresentation;
14
+ /** Title, description, body and footer density. @default "md" */
15
+ size?: DialogSize;
13
16
  className?: string;
14
17
  onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
15
18
  };
@@ -21,7 +24,7 @@ type ActionsProps = {
21
24
  tone?: DialogTone;
22
25
  onConfirm?: () => void;
23
26
  };
24
- export declare function Dialog({ open, onOpenChange, title, description, children, footer, presentation, className, onSubmit, }: DialogProps): import("react").JSX.Element;
27
+ export declare function Dialog({ open, onOpenChange, title, description, children, footer, presentation, size, className, onSubmit, }: DialogProps): import("react").JSX.Element;
25
28
  export declare namespace Dialog {
26
29
  export { Actions };
27
30
  }
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
+ export type DisclosureGroupSize = "sm" | "md" | "lg";
2
3
  export interface DisclosureGroupProps {
3
4
  label: string;
4
5
  open: boolean;
@@ -10,6 +11,8 @@ export interface DisclosureGroupProps {
10
11
  triggerClassName?: string;
11
12
  /** Supporting text beneath the title. */
12
13
  description?: string;
14
+ /** Trigger, description and content density. @default "md" */
15
+ size?: DisclosureGroupSize;
13
16
  }
14
17
  /** A controlled, accessible disclosure row with the shared inline reveal. */
15
- export declare function DisclosureGroup({ label, open, onOpenChange, children, disabled, className, triggerClassName, description, }: DisclosureGroupProps): import("react").JSX.Element;
18
+ export declare function DisclosureGroup({ label, open, onOpenChange, children, disabled, className, triggerClassName, description, size, }: DisclosureGroupProps): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export * from "./morph";
2
+ export * from "./use-morph-disclosure";
@@ -0,0 +1,29 @@
1
+ import { type HTMLMotionProps } from "motion/react";
2
+ import { type ReactNode, type RefObject } from "react";
3
+ export interface MorphProps extends Omit<HTMLMotionProps<"div">, "children"> {
4
+ /** Controls whether the overlay is presented. */
5
+ open: boolean;
6
+ /** Called on Escape or outside press. */
7
+ onClose: () => void;
8
+ /** The element whose rect the overlay grows from and collapses back to. */
9
+ sourceRef: RefObject<HTMLElement | null>;
10
+ /** Accessible name (used when there is no labelledBy). */
11
+ label: string;
12
+ /** ID of a visible heading inside the overlay. */
13
+ labelledBy?: string;
14
+ /** Allow tapping the backdrop to close. Escape still calls onClose. @default true */
15
+ dismissOnOutsidePress?: boolean;
16
+ className?: string;
17
+ children?: ReactNode;
18
+ }
19
+ /**
20
+ * A modal overlay that morphs its glass material from a source element's
21
+ * rect to its own resting rect, then collapses back — Apple's
22
+ * matched-geometry effect. The source is concealed while the overlay is
23
+ * mounted so the material reads as the source itself springing open.
24
+ *
25
+ * Reuses `SheetPortal` (theme inheritance), `useSheetFocus` (focus trap,
26
+ * Escape, modal environment) and the same `menuSurfaceReveal` FLIP
27
+ * primitive that powers anchored `Popover` and `ContextMenu`.
28
+ */
29
+ export declare function Morph({ open, ...props }: MorphProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Disclosure state that keeps the overlay mounted through its exit animation,
3
+ * mirroring `useAnchoredDisclosure`. Pair with a `useRef<HTMLElement>(null)`
4
+ * for the source.
5
+ */
6
+ export declare function useMorphDisclosure(disabled?: boolean): {
7
+ open: boolean;
8
+ mounted: boolean;
9
+ show: () => void;
10
+ toggle: () => void;
11
+ close: () => void;
12
+ onExited: () => void;
13
+ };
@@ -1,3 +1,4 @@
1
+ export type RadioGroupSize = "sm" | "md" | "lg";
1
2
  export interface RadioGroupOption<T extends string> {
2
3
  value: T;
3
4
  label: string;
@@ -12,6 +13,8 @@ export interface RadioGroupProps<T extends string> {
12
13
  disabled?: boolean;
13
14
  name?: string;
14
15
  className?: string;
16
+ /** Legend, row, radio glyph and description density. @default "md" */
17
+ size?: RadioGroupSize;
15
18
  }
16
19
  /** A labelled choice group using the browser's native radio keyboard behavior. */
17
- export declare function RadioGroup<T extends string>({ value, onChange, label, options, disabled, name, className, }: RadioGroupProps<T>): import("react").JSX.Element;
20
+ export declare function RadioGroup<T extends string>({ value, onChange, label, options, disabled, name, className, size, }: RadioGroupProps<T>): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ export type SegmentedControlSize = "sm" | "md" | "lg";
1
2
  export interface SegmentedControlProps<T extends string> {
2
3
  /** Value of the selected segment. */
3
4
  value: T;
@@ -12,6 +13,8 @@ export interface SegmentedControlProps<T extends string> {
12
13
  label: string;
13
14
  /** Custom classes for the control container. */
14
15
  className?: string;
16
+ /** Segment padding, label text scale and min-width. @default "md" */
17
+ size?: SegmentedControlSize;
15
18
  }
16
19
  /**
17
20
  * Mutually exclusive choices with a sliding selection, as on Apple
@@ -19,4 +22,4 @@ export interface SegmentedControlProps<T extends string> {
19
22
  * translation, a narrow pane) it becomes a pop-up button instead of
20
23
  * squeezing or spilling its text.
21
24
  */
22
- export declare function SegmentedControl<T extends string>({ value, options, onChange, label, className, }: SegmentedControlProps<T>): import("react").JSX.Element;
25
+ export declare function SegmentedControl<T extends string>({ value, options, onChange, label, className, size, }: SegmentedControlProps<T>): import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  export type SheetPlacement = "adaptive" | "center";
3
+ export type SheetSize = "sm" | "md" | "lg";
3
4
  export interface SheetProps {
4
5
  /** Controls whether the sheet is presented. */
5
6
  open: boolean;
@@ -13,6 +14,8 @@ export interface SheetProps {
13
14
  labelledBy?: string;
14
15
  /** `adaptive` rises from the bottom on phones; `center` always floats. @default "adaptive" */
15
16
  placement?: SheetPlacement;
17
+ /** Outer padding and panel max-width. @default "md" */
18
+ size?: SheetSize;
16
19
  className?: string;
17
20
  children: ReactNode;
18
21
  }
@@ -1,5 +1,6 @@
1
1
  import { type ComponentProps } from "react";
2
- export interface SliderProps extends Omit<ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange" | "min" | "max" | "step"> {
2
+ export type SliderSize = "sm" | "md" | "lg";
3
+ export interface SliderProps extends Omit<ComponentProps<"input">, "type" | "value" | "defaultValue" | "onChange" | "min" | "max" | "step" | "size"> {
3
4
  value: number;
4
5
  onChange: (value: number) => void;
5
6
  label: string;
@@ -9,6 +10,8 @@ export interface SliderProps extends Omit<ComponentProps<"input">, "type" | "val
9
10
  description?: string;
10
11
  /** Provides a localized, spoken description of the current value. */
11
12
  formatValue?: (value: number) => string;
13
+ /** Track height, thumb size and label/description text scale. @default "md" */
14
+ size?: SliderSize;
12
15
  }
13
16
  /** Native keyboard, touch and pointer behavior with Passui's control treatment. */
14
- export declare function Slider({ value, onChange, label, min, max, step, description, formatValue, id: suppliedId, className, disabled, ...props }: SliderProps): import("react").JSX.Element;
17
+ export declare function Slider({ value, onChange, label, min, max, step, description, formatValue, size, id: suppliedId, className, disabled, ...props }: SliderProps): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ export type StepperSize = "sm" | "md" | "lg";
1
2
  export interface StepperProps {
2
3
  value: number;
3
4
  onChange: (value: number) => void;
@@ -14,6 +15,8 @@ export interface StepperProps {
14
15
  className?: string;
15
16
  /** Formats the displayed value. */
16
17
  formatValue?: (value: number) => string;
18
+ /** Label, output and +/- button scale. @default "md" */
19
+ size?: StepperSize;
17
20
  }
18
21
  /** Discrete increments with disabled boundary actions and a spoken value. */
19
- export declare function Stepper({ value, onChange, label, labels, min, max, step, disabled, className, formatValue, }: StepperProps): import("react").JSX.Element;
22
+ export declare function Stepper({ value, onChange, label, labels, min, max, step, disabled, className, formatValue, size, }: StepperProps): import("react").JSX.Element;
@@ -6,6 +6,7 @@ export interface SwatchPickerOption<T extends string> {
6
6
  /** Color of the selected checkmark. */
7
7
  ink: string;
8
8
  }
9
+ export type SwatchPickerSize = "sm" | "md" | "lg";
9
10
  export interface SwatchPickerProps<T extends string> {
10
11
  value: T;
11
12
  options: ReadonlyArray<SwatchPickerOption<T>>;
@@ -14,6 +15,8 @@ export interface SwatchPickerProps<T extends string> {
14
15
  label: string;
15
16
  /** Custom classes for the swatch container. */
16
17
  className?: string;
18
+ /** Swatch diameter, check icon and caption scale. @default "md" */
19
+ size?: SwatchPickerSize;
17
20
  }
18
21
  /** Colour swatches with a check on the chosen one. */
19
- export declare function SwatchPicker<T extends string>({ value, options, onChange, label, className, }: SwatchPickerProps<T>): import("react").JSX.Element;
22
+ export declare function SwatchPicker<T extends string>({ value, options, onChange, label, className, size, }: SwatchPickerProps<T>): import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  export type TabViewAlign = "start" | "center";
3
+ export type TabViewSize = "sm" | "md" | "lg";
3
4
  export interface TabViewProps<Value extends string> {
4
5
  /** Available tabs, in keyboard navigation order. */
5
6
  options: readonly {
@@ -18,7 +19,9 @@ export interface TabViewProps<Value extends string> {
18
19
  label: string;
19
20
  /** Renders the selected tab's panel. */
20
21
  children: (value: Value) => ReactNode;
22
+ /** Tablist height, tab text scale and underline offset. @default "md" */
23
+ size?: TabViewSize;
21
24
  className?: string;
22
25
  }
23
26
  /** Content tabs with native buttons, roving focus and automatic keyboard activation. */
24
- export declare function TabView<Value extends string>({ options, defaultValue, value: controlledValue, onValueChange, align, label, children, className, }: TabViewProps<Value>): import("react").JSX.Element | null;
27
+ export declare function TabView<Value extends string>({ options, defaultValue, value: controlledValue, onValueChange, align, label, children, size, className, }: TabViewProps<Value>): import("react").JSX.Element | null;
@@ -1,4 +1,5 @@
1
1
  export type ToggleStyle = "switch" | "checkbox";
2
+ export type ToggleSize = "sm" | "md" | "lg";
2
3
  export interface ToggleProps {
3
4
  /** Current on or off state. */
4
5
  checked: boolean;
@@ -13,7 +14,9 @@ export interface ToggleProps {
13
14
  mixed?: boolean;
14
15
  /** Keeps the accessible name while hiding the visible label. @default false */
15
16
  labelHidden?: boolean;
17
+ /** Switch track size, thumb travel, checkbox box and label text scale. @default "md" */
18
+ size?: ToggleSize;
16
19
  /** Custom classes for the toggle container. */
17
20
  className?: string;
18
21
  }
19
- export declare function Toggle({ checked, onChange, label, disabled, style, mixed, labelHidden, className, }: ToggleProps): import("react").JSX.Element;
22
+ export declare function Toggle({ checked, onChange, label, disabled, style, mixed, labelHidden, size, className, }: ToggleProps): import("react").JSX.Element;
@@ -69,3 +69,4 @@ export * from "./components/slosh-slider/slosh-slider";
69
69
  export * from "./components/wake-slider/wake-slider";
70
70
  export * from "./components/one-time-code/one-time-code";
71
71
  export * from "./components/aspect-ratio/aspect-ratio";
72
+ export * from "./components/morph";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passui/react",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Passui: native React controls, design tokens, presentation and appearance.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,6 +62,10 @@
62
62
  "types": "./dist/types/src/components/popover/popover.d.ts",
63
63
  "default": "./src/components/popover/popover.tsx"
64
64
  },
65
+ "./morph": {
66
+ "types": "./dist/types/src/components/morph/index.d.ts",
67
+ "default": "./src/components/morph/index.ts"
68
+ },
65
69
  "./sheet": {
66
70
  "types": "./dist/types/src/components/sheet/sheet.d.ts",
67
71
  "default": "./src/components/sheet/sheet.tsx"
@@ -1,7 +1,15 @@
1
1
  import type { ReactNode } from "react"
2
- import { DisclosureGroup } from "../disclosure-group/disclosure-group"
2
+ import { DisclosureGroup, type DisclosureGroupSize } from "../disclosure-group/disclosure-group"
3
3
  import { cn } from "../../lib/class-names"
4
4
 
5
+ export type AccordionSize = DisclosureGroupSize
6
+
7
+ const containerRadius: Record<AccordionSize, string> = {
8
+ sm: "rounded-xl",
9
+ md: "rounded-2xl",
10
+ lg: "rounded-3xl",
11
+ }
12
+
5
13
  export interface AccordionItem {
6
14
  /** Stable value used to identify the expanded item. */
7
15
  value: string
@@ -17,15 +25,19 @@ export interface AccordionProps {
17
25
  value: string | null
18
26
  /** Selecting the open item reports `null`, so every item can collapse. */
19
27
  onValueChange: (value: string | null) => void
28
+ /** Density passed through to each DisclosureGroup. @default "md" */
29
+ size?: AccordionSize
20
30
  className?: string
21
31
  }
22
32
 
23
33
  /** A single-open, collapsible accordion with labelled animated content regions. */
24
- export function Accordion({ items, value, onValueChange, className }: AccordionProps) {
34
+ export function Accordion({ items, value, onValueChange, size = "md", className }: AccordionProps) {
25
35
  return (
26
36
  <div
27
37
  className={cn(
28
- "flex w-full min-w-0 flex-col overflow-hidden rounded-2xl border border-separator/60 bg-elevated",
38
+ "flex w-full min-w-0 flex-col overflow-hidden",
39
+ containerRadius[size],
40
+ "border border-separator/60 bg-elevated",
29
41
  className
30
42
  )}
31
43
  >
@@ -36,6 +48,7 @@ export function Accordion({ items, value, onValueChange, className }: AccordionP
36
48
  description={item.description}
37
49
  open={value === item.value}
38
50
  disabled={item.disabled}
51
+ size={size}
39
52
  triggerClassName="rounded-none"
40
53
  onOpenChange={(open) => onValueChange(open ? item.value : null)}
41
54
  className={cn(index > 0 && "border-t border-separator/60")}
@@ -1,7 +1,27 @@
1
1
  import { useState } from "react"
2
- import { TextField } from "../text-field/text-field"
2
+ import { TextField, type TextFieldSize } from "../text-field/text-field"
3
+ import { cn } from "../../lib/class-names"
3
4
  import { dateValue } from "./date-value"
4
5
 
6
+ const legendText: Record<TextFieldSize, string> = {
7
+ xs: "mb-2 text-caption font-medium",
8
+ sm: "mb-2 text-footnote font-medium",
9
+ md: "mb-3 text-subheadline font-medium",
10
+ lg: "mb-3 text-body font-medium",
11
+ }
12
+ const fieldsetGap: Record<TextFieldSize, string> = {
13
+ xs: "gap-2",
14
+ sm: "gap-2",
15
+ md: "gap-3",
16
+ lg: "gap-4",
17
+ }
18
+ const errorText: Record<TextFieldSize, string> = {
19
+ xs: "text-caption2 text-danger",
20
+ sm: "text-caption text-danger",
21
+ md: "text-footnote text-danger",
22
+ lg: "text-subheadline text-danger",
23
+ }
24
+
5
25
  type DatePickerProps = {
6
26
  value: string
7
27
  onChange: (value: string) => void
@@ -14,10 +34,20 @@ type DatePickerProps = {
14
34
  }
15
35
  locale: string
16
36
  max?: string
37
+ /** Field scale passed to each TextField; matches TextField's xs|sm|md|lg. @default "md" */
38
+ size?: TextFieldSize
17
39
  }
18
40
 
19
41
  /** Segmented date entry suited to birthdays, without a native calendar dialog. */
20
- export function DatePicker({ value, onChange, label, labels, locale, max }: DatePickerProps) {
42
+ export function DatePicker({
43
+ value,
44
+ onChange,
45
+ label,
46
+ labels,
47
+ locale,
48
+ max,
49
+ size = "md",
50
+ }: DatePickerProps) {
21
51
  const [year, month, day] = value.split("-")
22
52
  const [parts, setParts] = useState({
23
53
  year: year ?? "",
@@ -41,9 +71,9 @@ export function DatePicker({ value, onChange, label, labels, locale, max }: Date
41
71
  } => part.type === "year" || part.type === "month" || part.type === "day"
42
72
  )
43
73
  return (
44
- <fieldset className="flex min-w-0 flex-col gap-3">
45
- <legend className="mb-3 text-subheadline font-medium">{label}</legend>
46
- <div className="grid grid-cols-3 gap-3">
74
+ <fieldset className={cn("flex min-w-0 flex-col", fieldsetGap[size])}>
75
+ <legend className={legendText[size]}>{label}</legend>
76
+ <div className={cn("grid grid-cols-3", fieldsetGap[size])}>
47
77
  {order.map(({ type }) => (
48
78
  <TextField
49
79
  key={type}
@@ -52,6 +82,7 @@ export function DatePicker({ value, onChange, label, labels, locale, max }: Date
52
82
  autoComplete={`bday-${type}`}
53
83
  maxLength={type === "year" ? 4 : 2}
54
84
  value={parts[type]}
85
+ size={size}
55
86
  aria-invalid={invalid || undefined}
56
87
  onChange={(event) => {
57
88
  const next = {
@@ -65,7 +96,7 @@ export function DatePicker({ value, onChange, label, labels, locale, max }: Date
65
96
  ))}
66
97
  </div>
67
98
  {invalid && (
68
- <p role="status" className="text-footnote text-danger">
99
+ <p role="status" className={errorText[size]}>
69
100
  {labels.invalid}
70
101
  </p>
71
102
  )}
@@ -8,6 +8,28 @@ import { createContext, useContext, useId, type FormEvent, type ReactNode } from
8
8
  const CloseContext = createContext<() => void>(() => {})
9
9
  export type DialogPresentation = "drawer" | "center" | "inline"
10
10
  export type DialogTone = "default" | "destructive"
11
+ export type DialogSize = "sm" | "md" | "lg"
12
+
13
+ const titleText: Record<DialogSize, string> = {
14
+ sm: "text-subheadline",
15
+ md: "text-title3",
16
+ lg: "text-title2",
17
+ }
18
+ const descriptionText: Record<DialogSize, string> = {
19
+ sm: "text-caption",
20
+ md: "text-footnote",
21
+ lg: "text-subheadline",
22
+ }
23
+ const bodySpacing: Record<DialogSize, string> = {
24
+ sm: "gap-3 p-4",
25
+ md: "gap-4 p-5",
26
+ lg: "gap-5 p-6",
27
+ }
28
+ const footerSpacing: Record<DialogSize, string> = {
29
+ sm: "pt-2",
30
+ md: "pt-3",
31
+ lg: "pt-4",
32
+ }
11
33
 
12
34
  export type DialogProps = {
13
35
  open: boolean
@@ -18,6 +40,8 @@ export type DialogProps = {
18
40
  footer?: ReactNode
19
41
  /** `drawer` (responsive sheet), `center` (floating panel), or `inline`. @default "drawer" */
20
42
  presentation?: DialogPresentation
43
+ /** Title, description, body and footer density. @default "md" */
44
+ size?: DialogSize
21
45
  className?: string
22
46
  onSubmit?: (event: FormEvent<HTMLFormElement>) => void
23
47
  }
@@ -38,6 +62,7 @@ export function Dialog({
38
62
  children,
39
63
  footer,
40
64
  presentation = "drawer",
65
+ size = "md",
41
66
  className,
42
67
  onSubmit,
43
68
  }: DialogProps) {
@@ -47,13 +72,21 @@ export function Dialog({
47
72
  const content = (
48
73
  <>
49
74
  {presentation !== "inline" && (
50
- <h2 id={titleId} className="text-title3 font-semibold">
75
+ <h2 id={titleId} className={cn(titleText[size], "font-semibold")}>
51
76
  {title}
52
77
  </h2>
53
78
  )}
54
- {description && <div className="text-footnote text-label-secondary">{description}</div>}
79
+ {description && (
80
+ <div className={cn(descriptionText[size], "text-label-secondary")}>
81
+ {description}
82
+ </div>
83
+ )}
55
84
  {children && <div className="flex flex-col gap-4">{children}</div>}
56
- {footer && <div className="flex flex-wrap justify-end gap-2 pt-3">{footer}</div>}
85
+ {footer && (
86
+ <div className={cn("flex flex-wrap justify-end gap-2", footerSpacing[size])}>
87
+ {footer}
88
+ </div>
89
+ )}
57
90
  </>
58
91
  )
59
92
  const body = (
@@ -61,12 +94,22 @@ export function Dialog({
61
94
  {onSubmit ? (
62
95
  <form
63
96
  onSubmit={onSubmit}
64
- className={cn("flex min-h-0 flex-col gap-4 overflow-y-auto p-5", className)}
97
+ className={cn(
98
+ "flex min-h-0 flex-col overflow-y-auto",
99
+ bodySpacing[size],
100
+ className
101
+ )}
65
102
  >
66
103
  {content}
67
104
  </form>
68
105
  ) : (
69
- <div className={cn("flex min-h-0 flex-col gap-4 overflow-y-auto p-5", className)}>
106
+ <div
107
+ className={cn(
108
+ "flex min-h-0 flex-col overflow-y-auto",
109
+ bodySpacing[size],
110
+ className
111
+ )}
112
+ >
70
113
  {content}
71
114
  </div>
72
115
  )}
@@ -4,6 +4,29 @@ import { ChevronDownIcon } from "../../icons/icons"
4
4
  import { cn } from "../../lib/class-names"
5
5
  import { InlineDisclosure } from "../inline-disclosure/inline-disclosure"
6
6
 
7
+ export type DisclosureGroupSize = "sm" | "md" | "lg"
8
+
9
+ const triggerSizes: Record<DisclosureGroupSize, string> = {
10
+ sm: "h-auto min-h-9 w-full rounded-lg px-3 py-2 text-start text-footnote active:scale-100",
11
+ md: "h-auto min-h-11 w-full rounded-xl px-4 py-3 text-start text-subheadline active:scale-100",
12
+ lg: "h-auto min-h-13 w-full rounded-2xl px-5 py-4 text-start text-body active:scale-100",
13
+ }
14
+ const descriptionSizes: Record<DisclosureGroupSize, string> = {
15
+ sm: "text-caption2 leading-caption2 font-normal break-words whitespace-normal text-label-secondary",
16
+ md: "text-footnote leading-footnote font-normal break-words whitespace-normal text-label-secondary",
17
+ lg: "text-subheadline leading-subheadline font-normal break-words whitespace-normal text-label-secondary",
18
+ }
19
+ const contentSizes: Record<DisclosureGroupSize, string> = {
20
+ sm: "min-w-0 px-3 pb-3 text-footnote leading-footnote text-label-secondary [overflow-wrap:anywhere]",
21
+ md: "min-w-0 px-4 pb-4 text-subheadline leading-subheadline text-label-secondary [overflow-wrap:anywhere]",
22
+ lg: "min-w-0 px-5 pb-5 text-body leading-body text-label-secondary [overflow-wrap:anywhere]",
23
+ }
24
+ const chevronSizes: Record<DisclosureGroupSize, number> = {
25
+ sm: 15,
26
+ md: 17,
27
+ lg: 19,
28
+ }
29
+
7
30
  export interface DisclosureGroupProps {
8
31
  label: string
9
32
  open: boolean
@@ -15,6 +38,8 @@ export interface DisclosureGroupProps {
15
38
  triggerClassName?: string
16
39
  /** Supporting text beneath the title. */
17
40
  description?: string
41
+ /** Trigger, description and content density. @default "md" */
42
+ size?: DisclosureGroupSize
18
43
  }
19
44
 
20
45
  /** A controlled, accessible disclosure row with the shared inline reveal. */
@@ -27,6 +52,7 @@ export function DisclosureGroup({
27
52
  className,
28
53
  triggerClassName,
29
54
  description,
55
+ size = "md",
30
56
  }: DisclosureGroupProps) {
31
57
  const id = useId()
32
58
 
@@ -43,8 +69,8 @@ export function DisclosureGroup({
43
69
  disabled={disabled}
44
70
  onClick={() => onOpenChange(!open)}
45
71
  className={cn(
46
- "h-auto min-h-11 w-full rounded-xl px-4 py-3 text-start active:scale-100",
47
- "text-subheadline font-normal text-label hover:bg-fill/60 focus-visible:ring-inset focus-visible:ring-offset-0",
72
+ triggerSizes[size],
73
+ "font-normal text-label hover:bg-fill/60 focus-visible:ring-inset focus-visible:ring-offset-0",
48
74
  "focus-visible:outline-accent-text disabled:cursor-not-allowed disabled:opacity-40",
49
75
  triggerClassName
50
76
  )}
@@ -52,13 +78,11 @@ export function DisclosureGroup({
52
78
  <span className="flex min-w-0 flex-col gap-0.5">
53
79
  <span className="font-medium break-words whitespace-normal">{label}</span>
54
80
  {description && (
55
- <span className="text-footnote leading-footnote font-normal break-words whitespace-normal text-label-secondary">
56
- {description}
57
- </span>
81
+ <span className={descriptionSizes[size]}>{description}</span>
58
82
  )}
59
83
  </span>
60
84
  <ChevronDownIcon
61
- size={17}
85
+ size={chevronSizes[size]}
62
86
  aria-hidden="true"
63
87
  className={cn(
64
88
  "shrink-0 text-label-tertiary transition-transform duration-normal motion-reduce:transition-none",
@@ -69,9 +93,7 @@ export function DisclosureGroup({
69
93
  </h3>
70
94
  <section id={id} aria-labelledby={`${id}-trigger`} aria-hidden={!open} inert={!open}>
71
95
  <InlineDisclosure open={open}>
72
- <div className="min-w-0 px-4 pb-4 text-subheadline leading-subheadline text-label-secondary [overflow-wrap:anywhere]">
73
- {children}
74
- </div>
96
+ <div className={contentSizes[size]}>{children}</div>
75
97
  </InlineDisclosure>
76
98
  </section>
77
99
  </div>
@@ -0,0 +1,2 @@
1
+ export * from "./morph"
2
+ export * from "./use-morph-disclosure"