@passui/react 0.1.25 → 0.1.27

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.
@@ -10,6 +10,8 @@ export interface SegmentedControlProps<T extends string> {
10
10
  onChange: (value: NoInfer<T>) => void;
11
11
  /** Accessible name for the group and its compact picker. */
12
12
  label: string;
13
+ /** Custom classes for the control container. */
14
+ className?: string;
13
15
  }
14
16
  /**
15
17
  * Mutually exclusive choices with a sliding selection, as on Apple
@@ -17,4 +19,4 @@ export interface SegmentedControlProps<T extends string> {
17
19
  * translation, a narrow pane) it becomes a pop-up button instead of
18
20
  * squeezing or spilling its text.
19
21
  */
20
- export declare function SegmentedControl<T extends string>({ value, options, onChange, label, }: SegmentedControlProps<T>): import("react").JSX.Element;
22
+ export declare function SegmentedControl<T extends string>({ value, options, onChange, label, className, }: SegmentedControlProps<T>): import("react").JSX.Element;
@@ -10,6 +10,8 @@ export interface StatTileProps {
10
10
  /** Makes the tile a toggle, e.g. a filter. */
11
11
  onClick?: () => void;
12
12
  active?: boolean;
13
+ /** Custom classes for the tile container. */
14
+ className?: string;
13
15
  }
14
16
  /** One number with its label, optionally a toggle. */
15
- export declare function StatTile({ label, value, caption, tone, onClick, active }: StatTileProps): import("react").JSX.Element;
17
+ export declare function StatTile({ label, value, caption, tone, onClick, active, className, }: StatTileProps): import("react").JSX.Element;
@@ -12,6 +12,8 @@ export interface SwatchPickerProps<T extends string> {
12
12
  onChange: (value: T) => void;
13
13
  /** Accessible name for the group. */
14
14
  label: string;
15
+ /** Custom classes for the swatch container. */
16
+ className?: string;
15
17
  }
16
18
  /** Colour swatches with a check on the chosen one. */
17
- export declare function SwatchPicker<T extends string>({ value, options, onChange, label, }: SwatchPickerProps<T>): import("react").JSX.Element;
19
+ export declare function SwatchPicker<T extends string>({ value, options, onChange, label, className, }: SwatchPickerProps<T>): import("react").JSX.Element;
@@ -1,6 +1,9 @@
1
1
  import { type ComponentProps, type ReactNode } from "react";
2
+ /** Shared text control classes for components that reuse TextField's surface. */
2
3
  export declare const textControlClasses: string;
3
- export type TextFieldProps = ComponentProps<"input"> & {
4
+ /** Size scale shared across TextField, SecureField, and form fields. */
5
+ export type TextFieldSize = "xs" | "sm" | "md" | "lg";
6
+ export type TextFieldProps = Omit<ComponentProps<"input">, "size"> & {
4
7
  label: string;
5
8
  description?: ReactNode;
6
9
  errors?: string[];
@@ -16,5 +19,7 @@ export type TextFieldProps = ComponentProps<"input"> & {
16
19
  };
17
20
  /** Float the label into the outline on focus or when filled. @default "above" */
18
21
  labelPlacement?: "above" | "floating";
22
+ /** Field height and text scale. @default "md" */
23
+ size?: TextFieldSize;
19
24
  };
20
- export declare function TextField({ label, description, errors, icon, trailingIcon, action, revealLabels, labelPlacement, placeholder, type, id: suppliedId, className, ...props }: TextFieldProps): import("react").JSX.Element;
25
+ export declare function TextField({ label, description, errors, icon, trailingIcon, action, revealLabels, labelPlacement, size, placeholder, type, id: suppliedId, className, ...props }: TextFieldProps): import("react").JSX.Element;
@@ -13,5 +13,7 @@ export interface ToggleProps {
13
13
  mixed?: boolean;
14
14
  /** Keeps the accessible name while hiding the visible label. @default false */
15
15
  labelHidden?: boolean;
16
+ /** Custom classes for the toggle container. */
17
+ className?: string;
16
18
  }
17
- export declare function Toggle({ checked, onChange, label, disabled, style, mixed, labelHidden, }: ToggleProps): import("react").JSX.Element;
19
+ export declare function Toggle({ checked, onChange, label, disabled, style, mixed, labelHidden, className, }: ToggleProps): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passui/react",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Passui: native React controls, design tokens, presentation and appearance.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@ export function buttonStyle({
39
39
  className,
40
40
  }: Pick<ButtonProps, "variant" | "size" | "className"> = {}) {
41
41
  return cn(
42
- "group relative inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold tracking-[-0.005em]",
42
+ "group relative inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap overflow-hidden font-semibold tracking-[-0.005em]",
43
43
  "transition-[background-color,filter,transform,box-shadow] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] active:scale-[0.98]",
44
44
  "motion-reduce:transform-none motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-40",
45
45
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
@@ -87,7 +87,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
87
87
  {/* the label keeps its space, so the width never jumps */}
88
88
  <span
89
89
  className={cn(
90
- "inline-flex items-center justify-center gap-2 transition-opacity duration-200",
90
+ "inline-flex min-w-0 items-center justify-center gap-2 truncate transition-opacity duration-200",
91
91
  showPendingIndicator && "opacity-0",
92
92
  contentClassName
93
93
  )}
@@ -14,6 +14,8 @@ export interface SegmentedControlProps<T extends string> {
14
14
  onChange: (value: NoInfer<T>) => void
15
15
  /** Accessible name for the group and its compact picker. */
16
16
  label: string
17
+ /** Custom classes for the control container. */
18
+ className?: string
17
19
  }
18
20
 
19
21
  /**
@@ -27,6 +29,7 @@ export function SegmentedControl<T extends string>({
27
29
  options,
28
30
  onChange,
29
31
  label,
32
+ className,
30
33
  }: SegmentedControlProps<T>) {
31
34
  const id = useId()
32
35
  const reducedMotion = useReducedMotion()
@@ -47,7 +50,7 @@ export function SegmentedControl<T extends string>({
47
50
  }, [])
48
51
 
49
52
  return (
50
- <div ref={frame} className="relative w-full min-w-0">
53
+ <div ref={frame} className={cn("relative w-full min-w-0", className)}>
51
54
  {/* The control at its natural size, to know whether it fits. */}
52
55
  <div
53
56
  ref={measure}
@@ -13,10 +13,20 @@ export interface StatTileProps {
13
13
  /** Makes the tile a toggle, e.g. a filter. */
14
14
  onClick?: () => void
15
15
  active?: boolean
16
+ /** Custom classes for the tile container. */
17
+ className?: string
16
18
  }
17
19
 
18
20
  /** One number with its label, optionally a toggle. */
19
- export function StatTile({ label, value, caption, tone, onClick, active = false }: StatTileProps) {
21
+ export function StatTile({
22
+ label,
23
+ value,
24
+ caption,
25
+ tone,
26
+ onClick,
27
+ active = false,
28
+ className,
29
+ }: StatTileProps) {
20
30
  const body = (
21
31
  <>
22
32
  <span className="text-label-secondary">{label}</span>
@@ -26,17 +36,19 @@ export function StatTile({ label, value, caption, tone, onClick, active = false
26
36
  {caption && <span className="text-label-secondary">{caption}</span>}
27
37
  </>
28
38
  )
29
- const className =
30
- "flex min-w-0 flex-col gap-2 rounded-2xl border border-separator/35 bg-elevated p-4 text-left shadow-xs"
39
+ const tileClass = cn(
40
+ "flex min-w-0 flex-col gap-2 rounded-2xl border border-separator/35 bg-elevated p-4 text-left shadow-xs",
41
+ className
42
+ )
31
43
 
32
- if (!onClick) return <div className={className}>{body}</div>
44
+ if (!onClick) return <div className={tileClass}>{body}</div>
33
45
  return (
34
46
  <button
35
47
  type="button"
36
48
  aria-pressed={active}
37
49
  onClick={onClick}
38
50
  className={cn(
39
- className,
51
+ tileClass,
40
52
  "ring-1 ring-transparent transition-colors hover:bg-fill/60",
41
53
  active && "bg-fill/70 ring-label/15"
42
54
  )}
@@ -20,6 +20,8 @@ export interface SwatchPickerProps<T extends string> {
20
20
  onChange: (value: T) => void
21
21
  /** Accessible name for the group. */
22
22
  label: string
23
+ /** Custom classes for the swatch container. */
24
+ className?: string
23
25
  }
24
26
 
25
27
  /** Colour swatches with a check on the chosen one. */
@@ -28,10 +30,11 @@ export function SwatchPicker<T extends string>({
28
30
  options,
29
31
  onChange,
30
32
  label,
33
+ className,
31
34
  }: SwatchPickerProps<T>) {
32
35
  const name = useId()
33
36
  return (
34
- <fieldset className="flex flex-wrap gap-3">
37
+ <fieldset className={cn("flex flex-wrap gap-3", className)}>
35
38
  <legend className="sr-only">{label}</legend>
36
39
  {options.map((option) => {
37
40
  const selected = option.value === value
@@ -2,6 +2,7 @@ import { type ComponentProps, type ReactNode, useId, useState } from "react"
2
2
  import { Button } from "../button/button"
3
3
  import { cn } from "../../lib/class-names"
4
4
 
5
+ /** Shared text control classes for components that reuse TextField's surface. */
5
6
  export const textControlClasses = cn(
6
7
  "w-full min-w-0 rounded-xl border border-separator bg-elevated",
7
8
  "px-3 py-2 text-body text-label placeholder:text-label-tertiary",
@@ -11,7 +12,55 @@ export const textControlClasses = cn(
11
12
 
12
13
  const fieldIconClasses = "flex shrink-0 items-center text-label-secondary"
13
14
 
14
- export type TextFieldProps = ComponentProps<"input"> & {
15
+ /** Size scale shared across TextField, SecureField, and form fields. */
16
+ export type TextFieldSize = "xs" | "sm" | "md" | "lg"
17
+
18
+ interface FieldSize {
19
+ height: string
20
+ floatingHeight: string
21
+ padding: string
22
+ text: string
23
+ radius: string
24
+ }
25
+
26
+ 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
+ }
61
+ }
62
+
63
+ export type TextFieldProps = Omit<ComponentProps<"input">, "size"> & {
15
64
  label: string
16
65
  description?: ReactNode
17
66
  errors?: string[]
@@ -24,6 +73,8 @@ export type TextFieldProps = ComponentProps<"input"> & {
24
73
  revealLabels?: { show: string; hide: string }
25
74
  /** Float the label into the outline on focus or when filled. @default "above" */
26
75
  labelPlacement?: "above" | "floating"
76
+ /** Field height and text scale. @default "md" */
77
+ size?: TextFieldSize
27
78
  }
28
79
 
29
80
  export function TextField({
@@ -35,6 +86,7 @@ export function TextField({
35
86
  action,
36
87
  revealLabels,
37
88
  labelPlacement = "above",
89
+ size = "md",
38
90
  placeholder,
39
91
  type = "text",
40
92
  id: suppliedId,
@@ -46,6 +98,7 @@ export function TextField({
46
98
  const [revealed, setRevealed] = useState(false)
47
99
  const messages = Array.from(new Set(errors))
48
100
  const reveal = type === "password" && revealLabels
101
+ const dims = fieldSize(size)
49
102
  return (
50
103
  <div className="not-typeset flex flex-col gap-2">
51
104
  {labelPlacement === "above" && (
@@ -55,7 +108,7 @@ export function TextField({
55
108
  )}
56
109
  <div
57
110
  className={cn(
58
- "relative flex items-center gap-2 rounded-xl bg-elevated px-3",
111
+ `relative flex items-center gap-2 ${dims.padding} ${dims.radius} bg-elevated`,
59
112
  labelPlacement === "floating"
60
113
  ? "passui-floating-field"
61
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"
@@ -99,8 +152,9 @@ export function TextField({
99
152
  .join(" ") || undefined
100
153
  }
101
154
  className={cn(
102
- "h-11 w-full min-w-0 bg-transparent text-body text-label outline-none",
103
- labelPlacement === "floating" && "h-12",
155
+ labelPlacement === "floating" ? dims.floatingHeight : dims.height,
156
+ "w-full min-w-0 bg-transparent text-label outline-none",
157
+ dims.text,
104
158
  "placeholder:text-label-tertiary disabled:opacity-40",
105
159
  className
106
160
  )}
@@ -28,6 +28,8 @@ export interface ToggleProps {
28
28
  mixed?: boolean
29
29
  /** Keeps the accessible name while hiding the visible label. @default false */
30
30
  labelHidden?: boolean
31
+ /** Custom classes for the toggle container. */
32
+ className?: string
31
33
  }
32
34
 
33
35
  export function Toggle({
@@ -38,6 +40,7 @@ export function Toggle({
38
40
  style = "switch",
39
41
  mixed = false,
40
42
  labelHidden = false,
43
+ className,
41
44
  }: ToggleProps) {
42
45
  const input = useRef<HTMLInputElement>(null)
43
46
  useEffect(() => {
@@ -48,7 +51,8 @@ export function Toggle({
48
51
  <label
49
52
  className={cn(
50
53
  "flex cursor-pointer items-center gap-3 text-subheadline",
51
- disabled && "cursor-not-allowed opacity-40"
54
+ disabled && "cursor-not-allowed opacity-40",
55
+ className
52
56
  )}
53
57
  >
54
58
  <span className="relative inline-flex size-5 shrink-0">
@@ -82,7 +86,15 @@ export function Toggle({
82
86
  </label>
83
87
  )
84
88
  }
85
- return <SwitchToggle checked={checked} onChange={onChange} label={label} disabled={disabled} />
89
+ return (
90
+ <SwitchToggle
91
+ checked={checked}
92
+ onChange={onChange}
93
+ label={label}
94
+ disabled={disabled}
95
+ className={className}
96
+ />
97
+ )
86
98
  }
87
99
 
88
100
  function SwitchToggle({
@@ -90,7 +102,8 @@ function SwitchToggle({
90
102
  onChange,
91
103
  label,
92
104
  disabled,
93
- }: Pick<ToggleProps, "checked" | "onChange" | "label" | "disabled">) {
105
+ className,
106
+ }: Pick<ToggleProps, "checked" | "onChange" | "label" | "disabled" | "className">) {
94
107
  const reducedMotion = useReducedMotion()
95
108
  const id = `toggle-goo-${useId().replace(/:/g, "")}`
96
109
  const x = useMotionValue(checked ? 22 : 2)
@@ -163,7 +176,8 @@ function SwitchToggle({
163
176
  className={cn(
164
177
  "relative h-[30px] w-[50px] shrink-0 touch-pan-y rounded-full transition-colors duration-normal motion-reduce:transition-none",
165
178
  "shadow-[inset_0_1px_2px_rgb(0_0_0/0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:opacity-45",
166
- checked ? "bg-accent" : "bg-separator/80"
179
+ checked ? "bg-accent" : "bg-separator/80",
180
+ className
167
181
  )}
168
182
  >
169
183
  <svg aria-hidden="true" width="0" height="0" className="absolute">