@bug-on/m3-expressive 1.1.0 → 1.2.0

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.
@@ -0,0 +1,497 @@
1
+ import * as React$1 from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { ClassValue } from 'clsx';
5
+ import './md3-Dty-Qcad.js';
6
+ import { a as Typography, F as FontVariationAxes } from './typography-339RV6v7.js';
7
+
8
+ /**
9
+ * useMediaQuery — SSR-safe responsive hook.
10
+ *
11
+ * Prevents hydration mismatch by initializing with `false`.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const isMobile = useMediaQuery('(max-width: 768px)');
16
+ * ```
17
+ */
18
+ declare function useMediaQuery(query: string): boolean;
19
+
20
+ interface RippleOptions {
21
+ /** Ripple animation duration (ms). Default: 600 */
22
+ duration?: number;
23
+ /** Ripple color. Default: 'currentColor' */
24
+ color?: string;
25
+ /** Opacity. Default: 0.12 (MD3 standard) */
26
+ opacity?: number;
27
+ /** Disable ripple (e.g. when component is disabled) */
28
+ disabled?: boolean;
29
+ }
30
+ /**
31
+ * useRipple — Material Design 3 Expressive Ripple Effect
32
+ *
33
+ * Pure DOM hook with zero external animation dependencies.
34
+ * Note: Requires element to have `position: relative` and `overflow: hidden`.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const { rippleRef, onPointerDown } = useRipple();
39
+ * <button ref={rippleRef} onPointerDown={onPointerDown}>Click me</button>
40
+ * ```
41
+ */
42
+ declare function useRipple$1<T extends HTMLElement = HTMLElement>(options?: RippleOptions): {
43
+ rippleRef: React$1.RefObject<T | null>;
44
+ onPointerDown: (event: React.PointerEvent<T>) => void;
45
+ };
46
+
47
+ type ThemeMode = "light" | "dark" | "system";
48
+ /**
49
+ * Color scheme variants from Material Design 3.
50
+ * - `expressive` — MD3 Expressive 2025: high-chroma, intentionally detached from
51
+ * source color. **Default for this library.**
52
+ * - `tonal_spot` — Standard Android 12+ theme: low-to-medium colorfulness.
53
+ * - `vibrant` — More colorful than tonal_spot, hue from source.
54
+ * - `fidelity` — Stays close to the source color's hue and chroma.
55
+ * - `content` — Similar to fidelity, optimised for image-based content.
56
+ * - `monochrome` — No hue; all grays.
57
+ * - `neutral` — Near-neutral colors with subtle tint.
58
+ */
59
+ type SchemeVariant = "expressive" | "tonal_spot" | "vibrant" | "fidelity" | "content" | "monochrome" | "neutral";
60
+ /**
61
+ * Contrast level for the generated scheme.
62
+ * - `-1` — Minimum contrast (accessibility not guaranteed)
63
+ * - `0` — Standard (design as spec'd)
64
+ * - `0.5` — Medium contrast
65
+ * - `1` — Maximum contrast (WCAG AAA)
66
+ */
67
+ type ContrastLevel = -1 | 0 | 0.5 | 1;
68
+ /**
69
+ * Resolves the effective color scheme from a ThemeMode.
70
+ * When mode is "system", reads the OS preference via matchMedia.
71
+ * Returns "light" as the safe default in SSR environments.
72
+ */
73
+ declare function resolveMode(mode: ThemeMode): "light" | "dark";
74
+ interface MD3ColorScheme {
75
+ primary: string;
76
+ onPrimary: string;
77
+ primaryContainer: string;
78
+ onPrimaryContainer: string;
79
+ inversePrimary: string;
80
+ primaryFixed: string;
81
+ primaryFixedDim: string;
82
+ onPrimaryFixed: string;
83
+ onPrimaryFixedVariant: string;
84
+ /** Expressive dim tone for primary (v0.4.0+) */
85
+ primaryDim: string;
86
+ secondary: string;
87
+ onSecondary: string;
88
+ secondaryContainer: string;
89
+ onSecondaryContainer: string;
90
+ secondaryFixed: string;
91
+ secondaryFixedDim: string;
92
+ onSecondaryFixed: string;
93
+ onSecondaryFixedVariant: string;
94
+ /** Expressive dim tone for secondary (v0.4.0+) */
95
+ secondaryDim: string;
96
+ tertiary: string;
97
+ onTertiary: string;
98
+ tertiaryContainer: string;
99
+ onTertiaryContainer: string;
100
+ tertiaryFixed: string;
101
+ tertiaryFixedDim: string;
102
+ onTertiaryFixed: string;
103
+ onTertiaryFixedVariant: string;
104
+ /** Expressive dim tone for tertiary (v0.4.0+) */
105
+ tertiaryDim: string;
106
+ error: string;
107
+ onError: string;
108
+ errorContainer: string;
109
+ onErrorContainer: string;
110
+ surface: string;
111
+ onSurface: string;
112
+ surfaceVariant: string;
113
+ onSurfaceVariant: string;
114
+ surfaceTint: string;
115
+ /** Dimmed surface — darker than surface, lighter than surface-container. */
116
+ surfaceDim: string;
117
+ /** Brightened surface — lighter than surface. */
118
+ surfaceBright: string;
119
+ surfaceContainerLowest: string;
120
+ surfaceContainerLow: string;
121
+ surfaceContainer: string;
122
+ surfaceContainerHigh: string;
123
+ surfaceContainerHighest: string;
124
+ inverseSurface: string;
125
+ inverseOnSurface: string;
126
+ background: string;
127
+ onBackground: string;
128
+ outline: string;
129
+ outlineVariant: string;
130
+ shadow: string;
131
+ scrim: string;
132
+ }
133
+ interface GenerateM3ThemeOptions {
134
+ variant?: SchemeVariant;
135
+ contrastLevel?: ContrastLevel;
136
+ }
137
+ /**
138
+ * Generate a complete MD3 Expressive color scheme from a seed hex string.
139
+ *
140
+ * Uses Material Color Utilities v0.4.0 with SpecVersion 2025 by default.
141
+ *
142
+ * @param sourceColorHex - Seed color in hex format (e.g. `"#6750A4"`).
143
+ * @param mode - Light or dark scheme. Defaults to `"light"`.
144
+ * @param options - Optional variant + contrast overrides.
145
+ * - `variant` — defaults to `"expressive"` (MD3 Expressive 2025)
146
+ * - `contrastLevel` — defaults to `0` (standard)
147
+ *
148
+ * @example
149
+ * // Backward-compatible call (no options required)
150
+ * generateM3Theme("#6750A4")
151
+ *
152
+ * @example
153
+ * // Expressive with high contrast
154
+ * generateM3Theme("#6750A4", "dark", { contrastLevel: 1 })
155
+ */
156
+ declare function generateM3Theme(sourceColorHex: string, mode?: "light" | "dark", options?: GenerateM3ThemeOptions): MD3ColorScheme;
157
+ /**
158
+ * Apply an MD3 Expressive dynamic color scheme to the document root as CSS
159
+ * custom properties.
160
+ *
161
+ * Sets `--md-sys-color-*` tokens (used by components) and
162
+ * `--color-m3-*` tokens (used by Tailwind). Also sets the `data-theme`
163
+ * attribute for dark mode CSS selectors.
164
+ *
165
+ * @param sourceColorHex - Seed color hex string.
166
+ * @param mode - Light/dark/system mode. Defaults to `"light"`.
167
+ * @param root - Target element. Defaults to `document.documentElement`.
168
+ * @param options - Optional variant + contrast overrides.
169
+ */
170
+ declare function applyTheme(sourceColorHex: string, mode?: ThemeMode, root?: HTMLElement, options?: GenerateM3ThemeOptions): void;
171
+
172
+ /**
173
+ * MaterialSymbolsPreconnect
174
+ *
175
+ * Injects preconnect resource hints for Google Fonts CDN into <head>.
176
+ * Place this component AS EARLY AS POSSIBLE in the app tree, ideally
177
+ * directly inside <head> or root layout.
178
+ *
179
+ * WHY THIS MATTERS:
180
+ * If @import url() is inside a CSS file, browser must:
181
+ * 1. Parse HTML -> download JS bundle -> execute CSS -> hit @import -> start Google Fonts connection
182
+ * Preconnect hints allow browser to establish TCP handshake + TLS connection early at step 1,
183
+ * saving 100-500ms connection latency depending on network.
184
+ *
185
+ * USAGE:
186
+ * ```tsx
187
+ * // app/layout.tsx (Next.js) or index.html equivalent
188
+ * import { MaterialSymbolsPreconnect } from '@bug-on/m3-expressive';
189
+ *
190
+ * export default function RootLayout({ children }) {
191
+ * return (
192
+ * <html>
193
+ * <head>
194
+ * <MaterialSymbolsPreconnect />
195
+ * </head>
196
+ * <body>{children}</body>
197
+ * </html>
198
+ * );
199
+ * }
200
+ * ```
201
+ *
202
+ * NOTE: Use this component only with CDN mode.
203
+ * Self-hosted fonts do not require preconnecting to external origins.
204
+ */
205
+ interface MaterialSymbolsPreconnectProps {
206
+ /**
207
+ * Array of Material Symbols font variants to load.
208
+ * Include only variants used by application to minimize bandwidth.
209
+ * @default ["outlined"]
210
+ */
211
+ variants?: Array<"outlined" | "rounded" | "sharp">;
212
+ }
213
+ declare function MaterialSymbolsPreconnect({ variants, }: MaterialSymbolsPreconnectProps): react_jsx_runtime.JSX.Element;
214
+
215
+ declare function cn(...inputs: ClassValue[]): string;
216
+
217
+ /**
218
+ * Props cho component {@link Icon}.
219
+ *
220
+ * All variable font axes are mapped directly to `font-variation-settings`.
221
+ */
222
+ interface IconProps extends React$1.HTMLAttributes<HTMLSpanElement> {
223
+ /**
224
+ * Name of the Material Symbol in snake_case format.
225
+ * @example "home", "arrow_forward", "settings"
226
+ * @see https://fonts.google.com/icons
227
+ */
228
+ name: string;
229
+ /**
230
+ * Geometric style variant — maps to font family.
231
+ * @default "outlined"
232
+ */
233
+ variant?: "outlined" | "rounded" | "sharp";
234
+ /**
235
+ * `FILL` axis. `0` = outlined, `1` = filled.
236
+ * Features spring animation when `animateFill` is true.
237
+ * @default 0
238
+ */
239
+ fill?: 0 | 1;
240
+ /**
241
+ * `wght` axis — stroke weight. Should match surrounding text weight.
242
+ * @default 400
243
+ */
244
+ weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700;
245
+ /**
246
+ * `GRAD` axis — fine-tunes visual weight without affecting layout dimensions.
247
+ * Use `-25` on dark backgrounds to offset light halation effects.
248
+ * @default 0
249
+ */
250
+ grade?: -50 | -25 | 0 | 100 | 200;
251
+ /**
252
+ * `opsz` axis — optical size in dp. Used to set `font-size` if `size` is not specified.
253
+ * Match rendered pixel size for best quality.
254
+ * @default 24
255
+ */
256
+ opticalSize?: 20 | 24 | 40 | 48;
257
+ /**
258
+ * Direct `font-size` override in px. `opsz` axis still respects `opticalSize`.
259
+ * @example size={18} opticalSize={20}
260
+ */
261
+ size?: number | "inherit";
262
+ /**
263
+ * Enables smooth spring animation when toggling FILL axis (uses `SPRING_TRANSITION_FAST`).
264
+ * Requires `motion` dependency.
265
+ * @default false
266
+ * @example <Icon name="favorite" fill={isLiked ? 1 : 0} animateFill />
267
+ */
268
+ animateFill?: boolean;
269
+ }
270
+ /**
271
+ * Material Symbols (variable font) Icon component.
272
+ *
273
+ * Make sure to import the icon font CSS before usage:
274
+ * ```ts
275
+ * import '@bug-on/m3-expressive/material-symbols-cdn.css';
276
+ * ```
277
+ *
278
+ * @remarks
279
+ * - Use snake_case for icon names: `"arrow_forward"`, NOT `"ArrowForward"`.
280
+ * - `aria-hidden="true"` is attached automatically — provide screen reader labels on parent elements.
281
+ *
282
+ * @example
283
+ * ```tsx
284
+ * // Basic Icon
285
+ * <Icon name="home" />
286
+ *
287
+ * // Visual customization (filled, heavy weight)
288
+ * <Icon name="favorite" variant="rounded" fill={1} weight={300} />
289
+ *
290
+ * // Animated transition on state change
291
+ * <Icon name="bookmark" fill={saved ? 1 : 0} animateFill />
292
+ *
293
+ * // Custom size override
294
+ * <Icon name="close" size={18} opticalSize={20} />
295
+ *
296
+ * // Combination with other components
297
+ * <Button icon={<Icon name="add" />}>Add to cart</Button>
298
+ * ```
299
+ *
300
+ * @see https://fonts.google.com/icons
301
+ * @see https://m3.material.io/styles/icons/overview
302
+ */
303
+ declare const Icon: React$1.NamedExoticComponent<IconProps & React$1.RefAttributes<HTMLSpanElement>>;
304
+
305
+ /**
306
+ * Represents a single ripple wave instance with position and size metadata.
307
+ *
308
+ * @example
309
+ * ```tsx
310
+ * const ripple: RippleOrigin = { id: Date.now(), x: 50, y: 30, size: 200 };
311
+ * ```
312
+ */
313
+ interface RippleOrigin {
314
+ /** Unique identifier used as React key and for removal. */
315
+ id: number;
316
+ /** X coordinate of the pointer event relative to the container's left edge (px). */
317
+ x: number;
318
+ /** Y coordinate of the pointer event relative to the container's top edge (px). */
319
+ y: number;
320
+ /**
321
+ * Diameter of the ripple circle (px).
322
+ * Typically `Math.hypot(width, height) * 2` to ensure it fills the container.
323
+ */
324
+ size: number;
325
+ }
326
+ /**
327
+ * Props for the `Ripple` presentation component.
328
+ */
329
+ interface RippleProps {
330
+ /** Active ripple instances to render. Managed by the parent via `useRipple`. */
331
+ ripples: RippleOrigin[];
332
+ /** Called when a ripple's exit animation completes — remove it from state. */
333
+ onRippleDone: (id: number) => void;
334
+ /**
335
+ * Completely disables the ripple effect.
336
+ * Use this when the parent element is disabled or interaction is not desired.
337
+ * @default false
338
+ */
339
+ disabled?: boolean;
340
+ /**
341
+ * When `true`, the ripple respects the user's OS-level
342
+ * `prefers-reduced-motion` accessibility setting and renders nothing if active.
343
+ *
344
+ * Set to `false` to always show ripples regardless of system preference.
345
+ * @default true
346
+ */
347
+ respectSystemMotion?: boolean;
348
+ }
349
+ /**
350
+ * MD3 Expressive Ripple — animated touch-feedback wave layer.
351
+ *
352
+ * Renders absolutely-positioned ripple circles inside an `overflow-hidden`
353
+ * container. Must be placed as a direct child of the interactive element.
354
+ *
355
+ * @remarks
356
+ * - The parent element **must** have `overflow: hidden` and `position: relative`
357
+ * (or equivalent) for clipping to work correctly.
358
+ * - Set `disabled` to `true` on parent's disabled state to avoid stale ripples.
359
+ * - The ripple color is `currentColor` at 12% opacity — matching MD3 state layer spec.
360
+ *
361
+ * @example
362
+ * ```tsx
363
+ * const { ripples, onPointerDown, removeRipple } = useRippleState();
364
+ *
365
+ * <button onPointerDown={onPointerDown} className="relative overflow-hidden">
366
+ * <Ripple ripples={ripples} onRippleDone={removeRipple} />
367
+ * Click me
368
+ * </button>
369
+ * ```
370
+ *
371
+ * @see {@link useRippleState} for the state management hook
372
+ * @see https://m3.material.io/foundations/interaction/states/overview
373
+ */
374
+ declare function Ripple({ ripples, onRippleDone, disabled, respectSystemMotion, }: RippleProps): react_jsx_runtime.JSX.Element | null;
375
+ /**
376
+ * Options for configuring `useRippleState` behaviour.
377
+ */
378
+ interface UseRippleStateOptions {
379
+ /**
380
+ * When `true`, the ripple is suppressed — `onPointerDown` becomes a no-op.
381
+ * Use this to sync the ripple with the parent element's `disabled` state.
382
+ * @default false
383
+ */
384
+ disabled?: boolean;
385
+ }
386
+ /**
387
+ * `useRippleState` — state manager for MD3 Expressive ripple waves.
388
+ *
389
+ * Tracks active ripple instances and provides pointer event handlers.
390
+ * Pair with the `<Ripple>` component for rendering.
391
+ *
392
+ * @remarks
393
+ * This hook only manages ripple *state* (coordinates, size, lifecycle).
394
+ * The actual animation is handled by `<Ripple>` via Framer Motion.
395
+ * Respecting `prefers-reduced-motion` is handled by `<Ripple>` itself.
396
+ *
397
+ * @param options - Configuration options. See {@link UseRippleStateOptions}.
398
+ * @returns `{ ripples, onPointerDown, removeRipple }` — bind to the interactive element.
399
+ *
400
+ * @example
401
+ * ```tsx
402
+ * function MyButton({ disabled, children }) {
403
+ * const { ripples, onPointerDown, removeRipple } = useRippleState({ disabled });
404
+ *
405
+ * return (
406
+ * <button
407
+ * disabled={disabled}
408
+ * onPointerDown={onPointerDown}
409
+ * className="relative overflow-hidden"
410
+ * >
411
+ * <Ripple ripples={ripples} onRippleDone={removeRipple} disabled={disabled} />
412
+ * {children}
413
+ * </button>
414
+ * );
415
+ * }
416
+ * ```
417
+ *
418
+ * @see {@link Ripple} for the rendering component
419
+ */
420
+ declare function useRippleState(options?: UseRippleStateOptions): {
421
+ ripples: RippleOrigin[];
422
+ onPointerDown: (e: React$1.PointerEvent<HTMLElement>) => void;
423
+ removeRipple: (id: number) => void;
424
+ };
425
+ /**
426
+ * @deprecated Use `useRippleState` instead. This alias will be removed in a future version.
427
+ * @see {@link useRippleState}
428
+ */
429
+ declare const useRipple: typeof useRippleState;
430
+
431
+ interface ThemeContextValue {
432
+ sourceColor: string;
433
+ setSourceColor: (color: string) => void;
434
+ mode: ThemeMode;
435
+ setMode: (mode: ThemeMode) => void;
436
+ /** The resolved color scheme actually applied — always "light" or "dark". */
437
+ effectiveMode: "light" | "dark";
438
+ /** Active color scheme variant. */
439
+ variant: SchemeVariant;
440
+ /** Active contrast level. */
441
+ contrastLevel: ContrastLevel;
442
+ }
443
+ interface MD3ThemeProviderProps {
444
+ children: ReactNode;
445
+ sourceColor?: string;
446
+ defaultMode?: ThemeMode;
447
+ persistToLocalStorage?: boolean;
448
+ /**
449
+ * Color scheme variant controlling hue and chroma behaviour.
450
+ * - `"expressive"` — MD3 Expressive 2025 (high-chroma, detached from seed). **Default.**
451
+ * - `"tonal_spot"` — Standard Android 12+ theme.
452
+ * - `"vibrant"` | `"fidelity"` | `"content"` | `"monochrome"` | `"neutral"`
453
+ * @default "expressive"
454
+ */
455
+ variant?: SchemeVariant;
456
+ /**
457
+ * Contrast level for the generated scheme.
458
+ * - `0` — Standard (default)
459
+ * - `0.5` — Medium contrast
460
+ * - `1` — Maximum contrast (WCAG AAA)
461
+ * @default 0
462
+ */
463
+ contrastLevel?: ContrastLevel;
464
+ /**
465
+ * A fully custom `Typography` instance.
466
+ * When provided, `fontFamily` and `fontVariationAxes` are ignored.
467
+ */
468
+ typography?: Typography;
469
+ /**
470
+ * Override the CSS `font-family` for all typography styles.
471
+ * Ignored when `typography` prop is provided.
472
+ * @example "'Inter', sans-serif"
473
+ */
474
+ fontFamily?: string;
475
+ /**
476
+ * Variable font axes applied globally via `font-variation-settings`.
477
+ * Merged on top of defaults (`ROND: 100`). Ignored when `typography` is provided.
478
+ * @example { ROND: 50 }
479
+ */
480
+ fontVariationAxes?: FontVariationAxes;
481
+ /**
482
+ * When `true`, mounts `SnackbarHost` inside the provider and exposes
483
+ * `useSnackbar()` to all descendants — no separate `<SnackbarProvider>` needed.
484
+ *
485
+ * Opt-in, default `false`. For advanced usage (e.g., scoped snackbars or
486
+ * custom host positioning), keep this `false` and use `<SnackbarProvider>`
487
+ * or `<SnackbarHost>` directly.
488
+ *
489
+ * @default false
490
+ */
491
+ enableSnackbar?: boolean;
492
+ }
493
+ declare function MD3ThemeProvider({ children, sourceColor: initialSourceColor, defaultMode, persistToLocalStorage, variant, contrastLevel, typography: typographyProp, fontFamily, fontVariationAxes, enableSnackbar, }: MD3ThemeProviderProps): react_jsx_runtime.JSX.Element;
494
+ declare function useTheme(): ThemeContextValue;
495
+ declare function useThemeMode(): Pick<ThemeContextValue, "mode" | "setMode" | "effectiveMode">;
496
+
497
+ export { type ContrastLevel as C, type GenerateM3ThemeOptions as G, Icon as I, type MD3ColorScheme as M, Ripple as R, type SchemeVariant as S, type ThemeMode as T, type UseRippleStateOptions as U, type IconProps as a, MD3ThemeProvider as b, type MD3ThemeProviderProps as c, MaterialSymbolsPreconnect as d, type RippleOrigin as e, type RippleProps as f, applyTheme as g, cn as h, generateM3Theme as i, useMediaQuery as j, useRipple as k, useRippleState as l, useTheme as m, useThemeMode as n, resolveMode as r, useRipple$1 as u };