@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.
Files changed (140) hide show
  1. package/README.md +57 -46
  2. package/dist/types/src/components/accordion/accordion.d.ts +1 -1
  3. package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
  4. package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
  5. package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
  6. package/dist/types/src/components/popover/index.d.ts +41 -0
  7. package/dist/types/src/components/popover/popover-content.d.ts +32 -0
  8. package/dist/types/src/components/popover/popover-root.d.ts +22 -0
  9. package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
  10. package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
  11. package/dist/types/src/components/table/table.d.ts +1 -1
  12. package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
  13. package/dist/types/src/index.d.ts +1 -1
  14. package/dist/types/src/machines/popover.machine.d.ts +30 -0
  15. package/package.json +307 -307
  16. package/src/appearance/index.ts +2 -2
  17. package/src/appearance/platform.tsx +76 -60
  18. package/src/appearance/theme.tsx +409 -377
  19. package/src/components/accordion/accordion.tsx +58 -49
  20. package/src/components/ambient/ambient.tsx +25 -25
  21. package/src/components/ambient/index.ts +1 -1
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
  23. package/src/components/breadcrumb/breadcrumb.tsx +63 -57
  24. package/src/components/button/button-styles.ts +39 -32
  25. package/src/components/button/button.tsx +91 -84
  26. package/src/components/button/icon-button.tsx +27 -25
  27. package/src/components/button/pending-spinner.tsx +8 -8
  28. package/src/components/button/use-pending-indicator.ts +11 -11
  29. package/src/components/chip/chip.tsx +35 -30
  30. package/src/components/code-block/code-block.tsx +40 -39
  31. package/src/components/combo-box/combo-box.tsx +5 -5
  32. package/src/components/command/command.tsx +124 -94
  33. package/src/components/compact-date-picker/calendar-month.ts +75 -53
  34. package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
  35. package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
  36. package/src/components/compact-time-picker/time-value.ts +32 -29
  37. package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
  38. package/src/components/content-view/content-view.tsx +168 -159
  39. package/src/components/content-view/index.ts +1 -1
  40. package/src/components/context-menu/conceal-menu-source.ts +25 -25
  41. package/src/components/context-menu/context-menu-host.tsx +439 -398
  42. package/src/components/context-menu/context-menu-store.ts +125 -123
  43. package/src/components/context-menu/menu-navigation.ts +25 -18
  44. package/src/components/context-menu/menu-position.ts +33 -29
  45. package/src/components/context-menu/menu-reveal.ts +32 -32
  46. package/src/components/context-menu/use-context-menu.ts +63 -56
  47. package/src/components/conversation/conversation.tsx +265 -263
  48. package/src/components/conversation/index.ts +1 -1
  49. package/src/components/date-picker/date-picker.tsx +95 -94
  50. package/src/components/date-picker/date-value.ts +20 -10
  51. package/src/components/dialog/dialog-host.tsx +105 -103
  52. package/src/components/dialog/dialog-store.ts +37 -36
  53. package/src/components/dialog/dialog.tsx +192 -179
  54. package/src/components/dialog/index.ts +1 -1
  55. package/src/components/disclosure-group/disclosure-group.tsx +93 -86
  56. package/src/components/divider/divider.tsx +24 -22
  57. package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
  58. package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
  59. package/src/components/labeled-content/labeled-content.tsx +68 -68
  60. package/src/components/locale-switcher/index.ts +1 -1
  61. package/src/components/locale-switcher/locale-switcher.tsx +279 -251
  62. package/src/components/menu/menu.tsx +75 -58
  63. package/src/components/morph/index.ts +2 -2
  64. package/src/components/morph/morph.tsx +227 -201
  65. package/src/components/morph/use-morph-disclosure.ts +19 -19
  66. package/src/components/one-time-code/one-time-code.tsx +216 -200
  67. package/src/components/picker/picker-trigger.ts +16 -13
  68. package/src/components/picker/picker.tsx +371 -345
  69. package/src/components/popover/anchored-surface.tsx +253 -229
  70. package/src/components/popover/index.ts +50 -0
  71. package/src/components/popover/popover-content.tsx +334 -0
  72. package/src/components/popover/popover-fit.ts +58 -53
  73. package/src/components/popover/popover-root.tsx +97 -0
  74. package/src/components/popover/popover-trigger.tsx +71 -0
  75. package/src/components/profile-image/profile-image.tsx +80 -64
  76. package/src/components/progress-view/progress-view.tsx +61 -59
  77. package/src/components/progress-view/use-liquid-progress.ts +38 -38
  78. package/src/components/radio-group/radio-group.tsx +71 -68
  79. package/src/components/search-field/expanding-search-field.tsx +191 -177
  80. package/src/components/search-field/search-field.tsx +59 -50
  81. package/src/components/search-list/search-list.tsx +246 -236
  82. package/src/components/section/section.tsx +58 -58
  83. package/src/components/secure-field/secure-field.tsx +3 -3
  84. package/src/components/segmented-control/segmented-control.tsx +136 -136
  85. package/src/components/sheet/modal-environment.ts +71 -64
  86. package/src/components/sheet/sheet-portal.tsx +58 -53
  87. package/src/components/sheet/sheet.tsx +146 -141
  88. package/src/components/sheet/use-sheet-focus.ts +97 -82
  89. package/src/components/sidebar/sidebar.tsx +78 -78
  90. package/src/components/skeleton/skeleton.tsx +30 -21
  91. package/src/components/slider/slider.tsx +113 -104
  92. package/src/components/slosh-slider/liquid-step.ts +32 -31
  93. package/src/components/slosh-slider/slosh-slider.tsx +265 -228
  94. package/src/components/stat-tile/index.ts +1 -1
  95. package/src/components/stat-tile/stat-tile.tsx +79 -68
  96. package/src/components/status-indicator/status-indicator.tsx +40 -37
  97. package/src/components/stepper/rolling-digit.tsx +82 -68
  98. package/src/components/stepper/stepper.tsx +122 -113
  99. package/src/components/swatch-picker/swatch-picker.tsx +120 -120
  100. package/src/components/tab-bar/tab-bar.tsx +149 -148
  101. package/src/components/tab-view/tab-view.tsx +140 -137
  102. package/src/components/table/table.tsx +117 -98
  103. package/src/components/text-editor/text-editor.tsx +34 -29
  104. package/src/components/text-field/text-field.tsx +182 -180
  105. package/src/components/toast/toast-store.ts +127 -115
  106. package/src/components/toast/toaster.tsx +361 -347
  107. package/src/components/toggle/toggle.tsx +297 -282
  108. package/src/components/tooltip/tooltip-host.tsx +157 -150
  109. package/src/components/view-controller/index.ts +1 -1
  110. package/src/components/view-controller/view-controller.tsx +64 -53
  111. package/src/components/wake-slider/wake-slider.tsx +220 -205
  112. package/src/hooks/scroll/minimize-decision.ts +11 -7
  113. package/src/hooks/use-anchored-disclosure.ts +19 -19
  114. package/src/hooks/use-anchored-placement.ts +29 -25
  115. package/src/hooks/use-dismiss.ts +33 -29
  116. package/src/hooks/use-floating-dismiss.ts +24 -22
  117. package/src/hooks/use-media-query.ts +67 -60
  118. package/src/hooks/use-minimize-on-scroll.ts +69 -60
  119. package/src/hooks/use-visual-viewport.ts +63 -63
  120. package/src/hooks/viewport/types.ts +16 -16
  121. package/src/hooks/viewport/viewport-metrics.ts +16 -10
  122. package/src/icons/icons.tsx +440 -328
  123. package/src/index.ts +91 -91
  124. package/src/lib/anchored-placement.ts +70 -65
  125. package/src/lib/class-names.ts +14 -13
  126. package/src/lib/clipboard.ts +8 -7
  127. package/src/lib/date.ts +61 -60
  128. package/src/lib/floating-layers.ts +39 -24
  129. package/src/lib/haptics.ts +10 -10
  130. package/src/lib/language.ts +9 -9
  131. package/src/lib/range-value.ts +27 -21
  132. package/src/lib/screen-edges.ts +33 -30
  133. package/src/lib/shapes.ts +38 -34
  134. package/src/machines/popover.machine.ts +51 -0
  135. package/src/motion/transitions.ts +46 -43
  136. package/src/styles/code-block.css +69 -43
  137. package/src/styles/motion-controls.css +393 -191
  138. package/src/tokens.ts +1 -1
  139. package/dist/types/src/components/popover/popover.d.ts +0 -21
  140. package/src/components/popover/popover.tsx +0 -322
@@ -1,198 +1,200 @@
1
- import { type ComponentProps, type ReactNode, useId, useState } from "react"
2
- import { Button } from "../button/button"
3
- import { cn } from "../../lib/class-names"
1
+ import { type ComponentProps, type ReactNode, useId, useState } from "react";
2
+ import { Button } from "../button/button";
3
+ import { cn } from "../../lib/class-names";
4
4
 
5
5
  /** Shared text control classes for components that reuse TextField's surface. */
6
6
  export const textControlClasses = cn(
7
- "w-full min-w-0 rounded-xl border border-separator bg-elevated",
8
- "px-3 py-2 text-body text-label placeholder:text-label-tertiary",
9
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-text",
10
- "disabled:cursor-not-allowed disabled:opacity-40 aria-invalid:border-danger"
11
- )
7
+ "border-separator bg-elevated w-full min-w-0 rounded-xl border",
8
+ "text-body text-label placeholder:text-label-tertiary px-3 py-2",
9
+ "focus-visible:outline-accent-text focus-visible:outline-2 focus-visible:outline-offset-2",
10
+ "aria-invalid:border-danger disabled:cursor-not-allowed disabled:opacity-40",
11
+ );
12
12
 
13
- const fieldIconClasses = "flex shrink-0 items-center text-label-secondary"
13
+ const fieldIconClasses = "flex shrink-0 items-center text-label-secondary";
14
14
 
15
15
  /** Size scale shared across TextField, SecureField, and form fields. */
16
- export type TextFieldSize = "xs" | "sm" | "md" | "lg"
16
+ export type TextFieldSize = "xs" | "sm" | "md" | "lg";
17
17
 
18
18
  export interface FieldSize {
19
- height: string
20
- floatingHeight: string
21
- padding: string
22
- text: string
23
- radius: string
19
+ height: string;
20
+ floatingHeight: string;
21
+ padding: string;
22
+ text: string;
23
+ radius: string;
24
24
  }
25
25
 
26
26
  export function fieldSize(size: TextFieldSize): FieldSize {
27
- switch (size) {
28
- case "xs":
29
- return {
30
- height: "h-8",
31
- floatingHeight: "h-9",
32
- padding: "px-2.5",
33
- text: "text-caption",
34
- radius: "rounded-lg",
35
- }
36
- case "sm":
37
- return {
38
- height: "h-9",
39
- floatingHeight: "h-10",
40
- padding: "px-3",
41
- text: "text-subheadline",
42
- radius: "rounded-xl",
43
- }
44
- case "lg":
45
- return {
46
- height: "h-12",
47
- floatingHeight: "h-14",
48
- padding: "px-4",
49
- text: "text-body",
50
- radius: "rounded-xl",
51
- }
52
- default:
53
- return {
54
- height: "h-11",
55
- floatingHeight: "h-12",
56
- padding: "px-3",
57
- text: "text-body",
58
- radius: "rounded-xl",
59
- }
60
- }
27
+ switch (size) {
28
+ case "xs":
29
+ return {
30
+ height: "h-8",
31
+ floatingHeight: "h-9",
32
+ padding: "px-2.5",
33
+ text: "text-caption",
34
+ radius: "rounded-lg",
35
+ };
36
+ case "sm":
37
+ return {
38
+ height: "h-9",
39
+ floatingHeight: "h-10",
40
+ padding: "px-3",
41
+ text: "text-subheadline",
42
+ radius: "rounded-xl",
43
+ };
44
+ case "lg":
45
+ return {
46
+ height: "h-12",
47
+ floatingHeight: "h-14",
48
+ padding: "px-4",
49
+ text: "text-body",
50
+ radius: "rounded-xl",
51
+ };
52
+ default:
53
+ return {
54
+ height: "h-11",
55
+ floatingHeight: "h-12",
56
+ padding: "px-3",
57
+ text: "text-body",
58
+ radius: "rounded-xl",
59
+ };
60
+ }
61
61
  }
62
62
 
63
63
  export type TextFieldProps = Omit<ComponentProps<"input">, "size"> & {
64
- label: string
65
- description?: ReactNode
66
- errors?: string[]
67
- /** A decorative icon before the text, such as a mail glyph. */
68
- icon?: ReactNode
69
- /** A decorative icon after the text, such as a unit or a check. */
70
- trailingIcon?: ReactNode
71
- /** An interactive control after the text, such as a clear button. Size it to the field: an `xs` or `icon-xs` button, never the default. */
72
- action?: ReactNode
73
- revealLabels?: { show: string; hide: string }
74
- /** Float the label into the outline on focus or when filled. @default "above" */
75
- labelPlacement?: "above" | "floating"
76
- /** Field height and text scale. @default "md" */
77
- size?: TextFieldSize
78
- }
64
+ label: string;
65
+ description?: ReactNode;
66
+ errors?: string[];
67
+ /** A decorative icon before the text, such as a mail glyph. */
68
+ icon?: ReactNode;
69
+ /** A decorative icon after the text, such as a unit or a check. */
70
+ trailingIcon?: ReactNode;
71
+ /** An interactive control after the text, such as a clear button. Size it to the field: an `xs` or `icon-xs` button, never the default. */
72
+ action?: ReactNode;
73
+ revealLabels?: { show: string; hide: string };
74
+ /** Float the label into the outline on focus or when filled. @default "above" */
75
+ labelPlacement?: "above" | "floating";
76
+ /** Field height and text scale. @default "md" */
77
+ size?: TextFieldSize;
78
+ };
79
79
 
80
80
  export function TextField({
81
- label,
82
- description,
83
- errors = [],
84
- icon,
85
- trailingIcon,
86
- action,
87
- revealLabels,
88
- labelPlacement = "above",
89
- size = "md",
90
- placeholder,
91
- type = "text",
92
- id: suppliedId,
93
- className,
94
- ...props
81
+ label,
82
+ description,
83
+ errors = [],
84
+ icon,
85
+ trailingIcon,
86
+ action,
87
+ revealLabels,
88
+ labelPlacement = "above",
89
+ size = "md",
90
+ placeholder,
91
+ type = "text",
92
+ id: suppliedId,
93
+ className,
94
+ ...props
95
95
  }: TextFieldProps) {
96
- const generatedId = useId()
97
- const id = suppliedId ?? generatedId
98
- const [revealed, setRevealed] = useState(false)
99
- const messages = Array.from(new Set(errors))
100
- const reveal = type === "password" && revealLabels
101
- const dims = fieldSize(size)
102
- return (
103
- <div className="not-typeset flex flex-col gap-2">
104
- {labelPlacement === "above" && (
105
- <label htmlFor={id} className="text-subheadline font-semibold">
106
- {label}
107
- </label>
108
- )}
109
- <div
110
- className={cn(
111
- `relative flex items-center gap-2 ${dims.padding} ${dims.radius} bg-elevated`,
112
- labelPlacement === "floating"
113
- ? "passui-floating-field"
114
- : "border border-separator focus-within:ring-1 focus-within:ring-accent/40 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-accent-text/70"
115
- )}
116
- >
117
- {labelPlacement === "floating" && (
118
- <>
119
- <fieldset aria-hidden="true" className="passui-field-outline">
120
- <legend>
121
- <span>{label}</span>
122
- </legend>
123
- </fieldset>
124
- <label
125
- htmlFor={id}
126
- className={cn(
127
- "passui-floating-label",
128
- icon && "passui-floating-label-with-icon"
129
- )}
130
- >
131
- {label}
132
- </label>
133
- </>
134
- )}
135
- {icon && (
136
- <span aria-hidden className={fieldIconClasses}>
137
- {icon}
138
- </span>
139
- )}
140
- <input
141
- {...props}
142
- id={id}
143
- type={reveal && revealed ? "text" : type}
144
- placeholder={placeholder ?? (labelPlacement === "floating" ? " " : label)}
145
- aria-invalid={messages.length > 0 || props["aria-invalid"]}
146
- aria-describedby={
147
- [
148
- props["aria-describedby"],
149
- (messages.length || description) && `${id}-description`,
150
- ]
151
- .filter(Boolean)
152
- .join(" ") || undefined
153
- }
154
- className={cn(
155
- labelPlacement === "floating" ? dims.floatingHeight : dims.height,
156
- "w-full min-w-0 bg-transparent text-label outline-none",
157
- dims.text,
158
- "placeholder:text-label-tertiary disabled:opacity-40",
159
- className
160
- )}
161
- />
162
- {trailingIcon && (
163
- <span aria-hidden className={fieldIconClasses}>
164
- {trailingIcon}
165
- </span>
166
- )}
167
- {reveal && (
168
- <Button
169
- variant="ghost"
170
- size="xs"
171
- // sized to sit inside the 44px field; on touch a
172
- // wider invisible area keeps a 44px target
173
- className="-mr-1.5 shrink-0 whitespace-nowrap [@media(pointer:coarse)]:before:absolute [@media(pointer:coarse)]:before:-inset-1.5 [@media(pointer:coarse)]:before:content-['']"
174
- aria-controls={id}
175
- aria-label={revealed ? revealLabels.hide : revealLabels.show}
176
- aria-pressed={revealed}
177
- onClick={() => setRevealed(!revealed)}
178
- >
179
- {revealed ? revealLabels.hide : revealLabels.show}
180
- </Button>
181
- )}
182
- {action}
183
- </div>
184
- {(messages.length > 0 || description) && (
185
- <div
186
- id={`${id}-description`}
187
- role={messages.length ? "alert" : undefined}
188
- className={cn(
189
- "text-footnote",
190
- messages.length ? "text-danger" : "text-label-secondary"
191
- )}
192
- >
193
- {messages.length ? messages.join(". ") : description}
194
- </div>
195
- )}
196
- </div>
197
- )
96
+ const generatedId = useId();
97
+ const id = suppliedId ?? generatedId;
98
+ const [revealed, setRevealed] = useState(false);
99
+ const messages = Array.from(new Set(errors));
100
+ const reveal = type === "password" && revealLabels;
101
+ const dims = fieldSize(size);
102
+ return (
103
+ <div className="not-typeset flex flex-col gap-2">
104
+ {labelPlacement === "above" && (
105
+ <label htmlFor={id} className="text-subheadline font-semibold">
106
+ {label}
107
+ </label>
108
+ )}
109
+ <div
110
+ className={cn(
111
+ `relative flex items-center gap-2 ${dims.padding} ${dims.radius} bg-elevated`,
112
+ labelPlacement === "floating"
113
+ ? "passui-floating-field"
114
+ : "border-separator focus-within:ring-accent/40 has-[:focus-visible]:ring-accent-text/70 border focus-within:ring-1 has-[:focus-visible]:ring-2",
115
+ )}
116
+ >
117
+ {labelPlacement === "floating" && (
118
+ <>
119
+ <fieldset aria-hidden="true" className="passui-field-outline">
120
+ <legend>
121
+ <span>{label}</span>
122
+ </legend>
123
+ </fieldset>
124
+ <label
125
+ htmlFor={id}
126
+ className={cn(
127
+ "passui-floating-label",
128
+ icon && "passui-floating-label-with-icon",
129
+ )}
130
+ >
131
+ {label}
132
+ </label>
133
+ </>
134
+ )}
135
+ {icon && (
136
+ <span aria-hidden className={fieldIconClasses}>
137
+ {icon}
138
+ </span>
139
+ )}
140
+ <input
141
+ {...props}
142
+ id={id}
143
+ type={reveal && revealed ? "text" : type}
144
+ placeholder={
145
+ placeholder ?? (labelPlacement === "floating" ? " " : label)
146
+ }
147
+ aria-invalid={messages.length > 0 || props["aria-invalid"]}
148
+ aria-describedby={
149
+ [
150
+ props["aria-describedby"],
151
+ (messages.length || description) && `${id}-description`,
152
+ ]
153
+ .filter(Boolean)
154
+ .join(" ") || undefined
155
+ }
156
+ className={cn(
157
+ labelPlacement === "floating" ? dims.floatingHeight : dims.height,
158
+ "text-label w-full min-w-0 bg-transparent outline-none",
159
+ dims.text,
160
+ "placeholder:text-label-tertiary disabled:opacity-40",
161
+ className,
162
+ )}
163
+ />
164
+ {trailingIcon && (
165
+ <span aria-hidden className={fieldIconClasses}>
166
+ {trailingIcon}
167
+ </span>
168
+ )}
169
+ {reveal && (
170
+ <Button
171
+ variant="ghost"
172
+ size="xs"
173
+ // sized to sit inside the 44px field; on touch a
174
+ // wider invisible area keeps a 44px target
175
+ className="-mr-1.5 shrink-0 whitespace-nowrap [@media(pointer:coarse)]:before:absolute [@media(pointer:coarse)]:before:-inset-1.5 [@media(pointer:coarse)]:before:content-['']"
176
+ aria-controls={id}
177
+ aria-label={revealed ? revealLabels.hide : revealLabels.show}
178
+ aria-pressed={revealed}
179
+ onClick={() => setRevealed(!revealed)}
180
+ >
181
+ {revealed ? revealLabels.hide : revealLabels.show}
182
+ </Button>
183
+ )}
184
+ {action}
185
+ </div>
186
+ {(messages.length > 0 || description) && (
187
+ <div
188
+ id={`${id}-description`}
189
+ role={messages.length ? "alert" : undefined}
190
+ className={cn(
191
+ "text-footnote",
192
+ messages.length ? "text-danger" : "text-label-secondary",
193
+ )}
194
+ >
195
+ {messages.length ? messages.join(". ") : description}
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
198
200
  }