@opalui/react 0.0.0-stage → 0.0.1

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 (72) hide show
  1. package/dist/components/button/button.css +157 -0
  2. package/dist/components/button/button.css.js +1 -0
  3. package/dist/components/button/button.d.ts +26 -0
  4. package/dist/components/button/button.js +39 -0
  5. package/dist/components/button/button.js.map +1 -0
  6. package/dist/components/glass/glass.css +16 -0
  7. package/dist/components/glass/glass.css.js +1 -0
  8. package/dist/components/glass/glass.d.ts +5 -0
  9. package/dist/components/glass/glass.js +19 -0
  10. package/dist/components/glass/glass.js.map +1 -0
  11. package/dist/components/input/input.css +43 -0
  12. package/dist/components/input/input.css.js +1 -0
  13. package/dist/components/input/input.d.ts +17 -0
  14. package/dist/components/input/input.js +36 -0
  15. package/dist/components/input/input.js.map +1 -0
  16. package/dist/components/menu/menu.css +85 -0
  17. package/dist/components/menu/menu.css.js +1 -0
  18. package/dist/components/menu/menu.d.ts +18 -0
  19. package/dist/components/menu/menu.js +125 -0
  20. package/dist/components/menu/menu.js.map +1 -0
  21. package/dist/components/segmented-control/segmented-control.css +109 -0
  22. package/dist/components/segmented-control/segmented-control.css.js +1 -0
  23. package/dist/components/segmented-control/segmented-control.d.ts +21 -0
  24. package/dist/components/segmented-control/segmented-control.js +147 -0
  25. package/dist/components/segmented-control/segmented-control.js.map +1 -0
  26. package/dist/components/select/select.css +137 -0
  27. package/dist/components/select/select.css.js +1 -0
  28. package/dist/components/select/select.d.ts +27 -0
  29. package/dist/components/select/select.js +140 -0
  30. package/dist/components/select/select.js.map +1 -0
  31. package/dist/components/slider/slider.css +87 -0
  32. package/dist/components/slider/slider.css.js +1 -0
  33. package/dist/components/slider/slider.d.ts +17 -0
  34. package/dist/components/slider/slider.js +40 -0
  35. package/dist/components/slider/slider.js.map +1 -0
  36. package/dist/components/switch/switch.css +103 -0
  37. package/dist/components/switch/switch.css.js +1 -0
  38. package/dist/components/switch/switch.d.ts +14 -0
  39. package/dist/components/switch/switch.js +74 -0
  40. package/dist/components/switch/switch.js.map +1 -0
  41. package/dist/components/textarea/textarea.css +29 -0
  42. package/dist/components/textarea/textarea.css.js +1 -0
  43. package/dist/components/textarea/textarea.d.ts +8 -0
  44. package/dist/components/textarea/textarea.js +17 -0
  45. package/dist/components/textarea/textarea.js.map +1 -0
  46. package/dist/index.d.ts +9 -0
  47. package/dist/index.js +10 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/internal/glass/lens.d.ts +44 -0
  50. package/dist/internal/glass/lens.js +116 -0
  51. package/dist/internal/glass/lens.js.map +1 -0
  52. package/dist/internal/glass/map.d.ts +1 -0
  53. package/dist/internal/glass/map.js +48 -0
  54. package/dist/internal/glass/map.js.map +1 -0
  55. package/dist/internal/glass/material.d.ts +48 -0
  56. package/dist/internal/glass/material.js +22 -0
  57. package/dist/internal/glass/material.js.map +1 -0
  58. package/dist/internal/glass/styles.d.ts +2 -0
  59. package/dist/internal/glass/styles.js +4 -0
  60. package/dist/internal/glass/styles.js.map +1 -0
  61. package/dist/internal/glass/support.d.ts +5 -0
  62. package/dist/internal/glass/support.js +8 -0
  63. package/dist/internal/glass/support.js.map +1 -0
  64. package/dist/internal/glass.css +7 -0
  65. package/dist/internal/glass.css.js +1 -0
  66. package/dist/internal/styles.d.ts +2 -0
  67. package/dist/internal/styles.js +9 -0
  68. package/dist/internal/styles.js.map +1 -0
  69. package/dist/styles.css +11 -0
  70. package/dist/styles.css.js +1 -0
  71. package/package.json +90 -4
  72. package/README.md +0 -3
@@ -0,0 +1,157 @@
1
+ /* Shared material. Public variables can be set once on a theme container. */
2
+ :where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
3
+ --_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
4
+ --_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
5
+ }
6
+
7
+ :where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
8
+ :where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
9
+
10
+
11
+ /* The complete CSS material is server-rendered; lensing is optional. */
12
+ .opal-button-filter { position: absolute; pointer-events: none; }
13
+ .opal-button {
14
+ position: relative;
15
+ z-index: 0;
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: 0.5rem;
20
+ box-sizing: border-box;
21
+ min-height: 2.875rem;
22
+ padding: 0.75rem 1.125rem;
23
+ border: 0;
24
+ border-radius: var(--opal-button-radius, 999px);
25
+ background: transparent;
26
+ color: var(--opal-button-text-color, var(--opal-button-tint, var(--opal-button-color, #171e22)));
27
+ box-shadow: var(--opal-button-shadow, 0 2px 6px rgb(15 34 47 / 10%));
28
+ font: inherit;
29
+ font-weight: 400;
30
+ line-height: 1.25;
31
+ cursor: pointer;
32
+ touch-action: manipulation;
33
+ -webkit-tap-highlight-color: transparent;
34
+ transform: scale(1);
35
+ transition:
36
+ transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1),
37
+ box-shadow 450ms ease;
38
+ }
39
+
40
+ .opal-button[data-icon="leading"] { padding-inline-start: 0.875rem; }
41
+ .opal-button[data-icon="only"] {
42
+ width: 2.875rem;
43
+ height: 2.875rem;
44
+ padding: 0;
45
+ flex-shrink: 0;
46
+ }
47
+ .opal-button-icon {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ flex: 0 0 auto;
52
+ width: 1.25em;
53
+ height: 1.25em;
54
+ line-height: 1;
55
+ pointer-events: none;
56
+ }
57
+ .opal-button-icon > svg,
58
+ .opal-button-icon > img { display: block; width: 100%; height: 100%; }
59
+
60
+ .opal-button::before,
61
+ .opal-button::after {
62
+ content: "";
63
+ position: absolute;
64
+ inset: 0;
65
+ border-radius: inherit;
66
+ pointer-events: none;
67
+ }
68
+
69
+ .opal-button::before {
70
+ z-index: -1;
71
+ background: var(--opal-button-background, rgb(236 247 255 / 46%));
72
+ -webkit-backdrop-filter: blur(12px) saturate(115%);
73
+ backdrop-filter: var(--opal-material-filter, blur(var(--opal-material-frost, 12px))) saturate(115%);
74
+ transition: background-color 240ms ease;
75
+ }
76
+
77
+ .opal-button::after {
78
+ /* Diffuse light only along the edge; leave the center uniformly tinted. */
79
+ box-shadow: var(--opal-button-rim,
80
+ inset 0 0 0.6px 0.5px rgb(255 255 255 / 12%),
81
+ inset 0 0.75px 1.3px rgb(255 255 255 / 24%),
82
+ inset 0 -0.75px 1.3px rgb(255 255 255 / 14%));
83
+ opacity: 0.7;
84
+ transition: opacity 450ms ease;
85
+ }
86
+
87
+ .opal-button:active:not(:disabled)::after { opacity: 1; }
88
+
89
+ /* A crisp upper-left reflection gives glass a curved edge without adding
90
+ another gradient across the face. Theme-specific rims can override it. */
91
+ .opal-button[data-variant="glass"]::after {
92
+ box-shadow: var(--opal-button-rim, var(--_opal-glass-rim));
93
+ }
94
+ .opal-button[data-variant="glass"]:not(:active) {
95
+ box-shadow: var(--opal-button-shadow, var(--_opal-glass-shadow));
96
+ }
97
+
98
+ @media (hover: hover) {
99
+ .opal-button:not([data-variant="filled"]):hover:not(:disabled)::before {
100
+ background-color: var(--opal-button-hover-background, rgb(236 247 255 / 56%));
101
+ }
102
+ }
103
+
104
+ .opal-button:active:not(:disabled) {
105
+ transform: scale(var(--opal-button-press-scale, 1.045));
106
+ transition-duration: 360ms, 450ms;
107
+ box-shadow: var(--opal-button-pressed-shadow, 0 4px 10px rgb(15 34 47 / 14%));
108
+ }
109
+
110
+ /* Filled is opaque in every state and does not sample the backdrop. */
111
+ .opal-button[data-variant="filled"] {
112
+ color: var(--opal-button-text-color, var(--opal-button-color, var(--opal-button-filled-foreground, #fff)));
113
+ }
114
+ .opal-button[data-variant="filled"]::before {
115
+ background: var(--opal-button-tint, var(--opal-button-filled-background, #007aff));
116
+ box-shadow:
117
+ inset 0 1px 2px rgb(255 255 255 / 24%),
118
+ inset 0 -1px 2px rgb(255 255 255 / 10%);
119
+ -webkit-backdrop-filter: none;
120
+ backdrop-filter: none;
121
+ transition: filter 240ms ease;
122
+ }
123
+ @media (hover: hover) {
124
+ .opal-button[data-variant="filled"]:hover:not(:disabled)::before { filter: brightness(1.08); }
125
+ }
126
+
127
+ .opal-button:focus-visible {
128
+ outline: 2px solid var(--opal-focus-color, currentColor);
129
+ outline-offset: 4px;
130
+ }
131
+
132
+ .opal-button:disabled { opacity: 0.4; cursor: not-allowed; }
133
+
134
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
135
+ .opal-button:not([data-variant="filled"])::before, .opal-button:not([data-variant="filled"]):hover:not(:disabled)::before {
136
+ background-color: var(--opal-button-fallback-background, #ededed);
137
+ }
138
+ }
139
+
140
+ @media (prefers-reduced-motion: reduce) {
141
+ .opal-button, .opal-button::before, .opal-button::after { transition: none; }
142
+ .opal-button:active:not(:disabled) { transform: none; }
143
+ }
144
+
145
+ @media (prefers-reduced-transparency: reduce) {
146
+ .opal-button:not([data-variant="filled"])::before, .opal-button:not([data-variant="filled"]):hover:not(:disabled)::before {
147
+ background: var(--opal-button-fallback-background, #ededed);
148
+ -webkit-backdrop-filter: none;
149
+ backdrop-filter: none;
150
+ }
151
+ }
152
+
153
+ @media (forced-colors: active) {
154
+ .opal-button[data-variant] { border: 1px solid ButtonText; background: ButtonFace; color: ButtonText; box-shadow: none; }
155
+ .opal-button::before, .opal-button::after { display: none; }
156
+ .opal-button:disabled { color: GrayText; border-color: GrayText; opacity: 1; }
157
+ }
@@ -0,0 +1 @@
1
+ export default "/* The complete CSS material is server-rendered; lensing is optional. */\n.opal-button-filter { position: absolute; pointer-events: none; }\n.opal-button {\n position: relative;\n z-index: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n box-sizing: border-box;\n min-height: 2.875rem;\n padding: 0.75rem 1.125rem;\n border: 0;\n border-radius: var(--opal-button-radius, 999px);\n background: transparent;\n color: var(--opal-button-text-color, var(--opal-button-tint, var(--opal-button-color, #171e22)));\n box-shadow: var(--opal-button-shadow, 0 2px 6px rgb(15 34 47 / 10%));\n font: inherit;\n font-weight: 400;\n line-height: 1.25;\n cursor: pointer;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transform: scale(1);\n transition:\n transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1),\n box-shadow 450ms ease;\n}\n\n.opal-button[data-icon=\"leading\"] { padding-inline-start: 0.875rem; }\n.opal-button[data-icon=\"only\"] {\n width: 2.875rem;\n height: 2.875rem;\n padding: 0;\n flex-shrink: 0;\n}\n.opal-button-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: 1.25em;\n height: 1.25em;\n line-height: 1;\n pointer-events: none;\n}\n.opal-button-icon > svg,\n.opal-button-icon > img { display: block; width: 100%; height: 100%; }\n\n.opal-button::before,\n.opal-button::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.opal-button::before {\n z-index: -1;\n background: var(--opal-button-background, rgb(236 247 255 / 46%));\n -webkit-backdrop-filter: blur(12px) saturate(115%);\n backdrop-filter: var(--opal-material-filter, blur(var(--opal-material-frost, 12px))) saturate(115%);\n transition: background-color 240ms ease;\n}\n\n.opal-button::after {\n /* Diffuse light only along the edge; leave the center uniformly tinted. */\n box-shadow: var(--opal-button-rim,\n inset 0 0 0.6px 0.5px rgb(255 255 255 / 12%),\n inset 0 0.75px 1.3px rgb(255 255 255 / 24%),\n inset 0 -0.75px 1.3px rgb(255 255 255 / 14%));\n opacity: 0.7;\n transition: opacity 450ms ease;\n}\n\n.opal-button:active:not(:disabled)::after { opacity: 1; }\n\n/* A crisp upper-left reflection gives glass a curved edge without adding\n another gradient across the face. Theme-specific rims can override it. */\n.opal-button[data-variant=\"glass\"]::after {\n box-shadow: var(--opal-button-rim, var(--_opal-glass-rim));\n}\n.opal-button[data-variant=\"glass\"]:not(:active) {\n box-shadow: var(--opal-button-shadow, var(--_opal-glass-shadow));\n}\n\n@media (hover: hover) {\n .opal-button:not([data-variant=\"filled\"]):hover:not(:disabled)::before {\n background-color: var(--opal-button-hover-background, rgb(236 247 255 / 56%));\n }\n}\n\n.opal-button:active:not(:disabled) {\n transform: scale(var(--opal-button-press-scale, 1.045));\n transition-duration: 360ms, 450ms;\n box-shadow: var(--opal-button-pressed-shadow, 0 4px 10px rgb(15 34 47 / 14%));\n}\n\n/* Filled is opaque in every state and does not sample the backdrop. */\n.opal-button[data-variant=\"filled\"] {\n color: var(--opal-button-text-color, var(--opal-button-color, var(--opal-button-filled-foreground, #fff)));\n}\n.opal-button[data-variant=\"filled\"]::before {\n background: var(--opal-button-tint, var(--opal-button-filled-background, #007aff));\n box-shadow:\n inset 0 1px 2px rgb(255 255 255 / 24%),\n inset 0 -1px 2px rgb(255 255 255 / 10%);\n -webkit-backdrop-filter: none;\n backdrop-filter: none;\n transition: filter 240ms ease;\n}\n@media (hover: hover) {\n .opal-button[data-variant=\"filled\"]:hover:not(:disabled)::before { filter: brightness(1.08); }\n}\n\n.opal-button:focus-visible {\n outline: 2px solid var(--opal-focus-color, currentColor);\n outline-offset: 4px;\n}\n\n.opal-button:disabled { opacity: 0.4; cursor: not-allowed; }\n\n@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {\n .opal-button:not([data-variant=\"filled\"])::before, .opal-button:not([data-variant=\"filled\"]):hover:not(:disabled)::before {\n background-color: var(--opal-button-fallback-background, #ededed);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .opal-button, .opal-button::before, .opal-button::after { transition: none; }\n .opal-button:active:not(:disabled) { transform: none; }\n}\n\n@media (prefers-reduced-transparency: reduce) {\n .opal-button:not([data-variant=\"filled\"])::before, .opal-button:not([data-variant=\"filled\"]):hover:not(:disabled)::before {\n background: var(--opal-button-fallback-background, #ededed);\n -webkit-backdrop-filter: none;\n backdrop-filter: none;\n }\n}\n\n@media (forced-colors: active) {\n .opal-button[data-variant] { border: 1px solid ButtonText; background: ButtonFace; color: ButtonText; box-shadow: none; }\n .opal-button::before, .opal-button::after { display: none; }\n .opal-button:disabled { color: GrayText; border-color: GrayText; opacity: 1; }\n}\n";
@@ -0,0 +1,26 @@
1
+ import { type ReactNode, type ComponentPropsWithoutRef } from 'react';
2
+ export type ButtonProps = ComponentPropsWithoutRef<'button'> & {
3
+ /** Glass is the default; filled uses an opaque background. */
4
+ variant?: 'glass' | 'filled';
5
+ /** Leading icon. Without a label, renders a circular button; supply aria-label. */
6
+ icon?: ReactNode;
7
+ /** An opaque #RGB or #RRGGBB color. Glass: text. Filled: background. */
8
+ tint?: `#${string}`;
9
+ /** Explicit #RGB or #RRGGBB text color; overrides tint and automatic text-color selection. */
10
+ textColor?: `#${string}`;
11
+ /** Glass color separation: 0 disables it, 0.6 is subtle, 4 exaggerates it. */
12
+ chromaticAberration?: number;
13
+ };
14
+ /** A native button with OpalUI styling. Styles are included automatically. */
15
+ export declare const Button: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
16
+ /** Glass is the default; filled uses an opaque background. */
17
+ variant?: "glass" | "filled";
18
+ /** Leading icon. Without a label, renders a circular button; supply aria-label. */
19
+ icon?: ReactNode;
20
+ /** An opaque #RGB or #RRGGBB color. Glass: text. Filled: background. */
21
+ tint?: `#${string}`;
22
+ /** Explicit #RGB or #RRGGBB text color; overrides tint and automatic text-color selection. */
23
+ textColor?: `#${string}`;
24
+ /** Glass color separation: 0 disables it, 0.6 is subtle, 4 exaggerates it. */
25
+ chromaticAberration?: number;
26
+ } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Children, forwardRef, useImperativeHandle, useRef } from 'react';
4
+ import { stylesheetHref } from '../../internal/styles.js';
5
+ import { useGlassLens } from '../../internal/glass/lens.js';
6
+ import { materials } from '../../internal/glass/material.js';
7
+ import componentCss from './button.css.js';
8
+ import glassCss from '../../internal/glass/styles.js';
9
+ const css = `${glassCss}\n${componentCss}`;
10
+ const styleHref = stylesheetHref('button', css);
11
+ function isHexColor(value) {
12
+ return !!value && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(value);
13
+ }
14
+ function tintStyles(tint) {
15
+ if (!isHexColor(tint))
16
+ return {};
17
+ const hex = tint.length === 4 ? tint.slice(1).split('').map(char => char + char).join('') : tint.slice(1);
18
+ const channels = [0, 2, 4].map(offset => {
19
+ const value = parseInt(hex.slice(offset, offset + 2), 16) / 255;
20
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
21
+ });
22
+ const luminance = channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
23
+ return {
24
+ '--opal-button-tint': tint,
25
+ // Appearance-first: keep white on saturated midtone fills (including
26
+ // orange/red), switching to black for light fills. Not a WCAG guarantee.
27
+ '--opal-button-filled-foreground': luminance > 0.55 ? '#000' : '#fff',
28
+ };
29
+ }
30
+ /** A native button with OpalUI styling. Styles are included automatically. */
31
+ export const Button = forwardRef(function Button({ className, style, icon, children, variant = 'glass', tint, textColor, chromaticAberration = 0.6, type = 'button', ...props }, ref) {
32
+ const hasIcon = Children.toArray(icon).length > 0;
33
+ const hasLabel = Children.toArray(children).some(child => typeof child !== 'string' || child.trim().length > 0);
34
+ const element = useRef(null);
35
+ useImperativeHandle(ref, () => element.current, []);
36
+ const glass = useGlassLens(element, { ...materials.surface, dispersion: Math.max(0, Math.min(4, Number.isFinite(chromaticAberration) ? chromaticAberration : .6)) / 5 }, { enabled: variant === 'glass' });
37
+ return (_jsxs(_Fragment, { children: [_jsx("style", { href: styleHref, precedence: "opalui", children: css }), glass.filter, _jsxs("button", { ...props, ref: element, "data-variant": variant, "data-icon": hasIcon ? (hasLabel ? "leading" : "only") : undefined, style: { ...tintStyles(tint), ...(isHexColor(textColor) ? { '--opal-button-text-color': textColor } : {}), ...glass.surfaceStyle, ...style }, type: type, className: ['opal-button', className].filter(Boolean).join(' '), children: [hasIcon && _jsx("span", { className: "opal-button-icon", "aria-hidden": "true", children: icon }), children] })] }));
38
+ });
39
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../../../src/components/button/button.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport { Children, forwardRef, useImperativeHandle, useRef, type CSSProperties, type ReactNode, type ComponentPropsWithoutRef } from 'react'\nimport { stylesheetHref } from '../../internal/styles.js'\nimport { useGlassLens } from '../../internal/glass/lens.js'\nimport { materials } from '../../internal/glass/material.js'\nimport componentCss from './button.css?inline'\nimport glassCss from '../../internal/glass/styles.js'\nconst css = `${glassCss}\\n${componentCss}`\n\nconst styleHref = stylesheetHref('button', css)\n\nexport type ButtonProps = ComponentPropsWithoutRef<'button'> & {\n /** Glass is the default; filled uses an opaque background. */\n variant?: 'glass' | 'filled'\n /** Leading icon. Without a label, renders a circular button; supply aria-label. */\n icon?: ReactNode\n /** An opaque #RGB or #RRGGBB color. Glass: text. Filled: background. */\n tint?: `#${string}`\n /** Explicit #RGB or #RRGGBB text color; overrides tint and automatic text-color selection. */\n textColor?: `#${string}`\n /** Glass color separation: 0 disables it, 0.6 is subtle, 4 exaggerates it. */\n chromaticAberration?: number\n}\n\nfunction isHexColor(value: string | undefined): value is string {\n return !!value && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(value)\n}\n\nfunction tintStyles(tint: ButtonProps['tint']): CSSProperties {\n if (!isHexColor(tint)) return {}\n const hex = tint.length === 4 ? tint.slice(1).split('').map(char => char + char).join('') : tint.slice(1)\n const channels = [0, 2, 4].map(offset => {\n const value = parseInt(hex.slice(offset, offset + 2), 16) / 255\n return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n })\n const luminance = channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722\n return {\n '--opal-button-tint': tint,\n // Appearance-first: keep white on saturated midtone fills (including\n // orange/red), switching to black for light fills. Not a WCAG guarantee.\n '--opal-button-filled-foreground': luminance > 0.55 ? '#000' : '#fff',\n } as CSSProperties\n}\n\n/** A native button with OpalUI styling. Styles are included automatically. */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button({ className, style, icon, children, variant = 'glass', tint, textColor, chromaticAberration = 0.6, type = 'button', ...props }, ref) {\n const hasIcon = Children.toArray(icon).length > 0\n const hasLabel = Children.toArray(children).some(child => typeof child !== 'string' || child.trim().length > 0)\n const element = useRef<HTMLButtonElement>(null)\n useImperativeHandle(ref, () => element.current!, [])\n const glass = useGlassLens(element, { ...materials.surface, dispersion: Math.max(0, Math.min(4, Number.isFinite(chromaticAberration) ? chromaticAberration : .6)) / 5 }, { enabled: variant === 'glass' })\n return (\n <>\n <style href={styleHref} precedence=\"opalui\">{css}</style>\n {glass.filter}\n <button\n {...props}\n ref={element}\n data-variant={variant}\n data-icon={hasIcon ? (hasLabel ? \"leading\" : \"only\") : undefined}\n style={{ ...tintStyles(tint), ...(isHexColor(textColor) ? { '--opal-button-text-color': textColor } : {}), ...glass.surfaceStyle, ...style } as CSSProperties}\n type={type}\n className={['opal-button', className].filter(Boolean).join(' ')}\n >\n {hasIcon && <span className=\"opal-button-icon\" aria-hidden=\"true\">{icon}</span>}\n {children}\n </button>\n </>\n )\n },\n)\n"]}
@@ -0,0 +1,16 @@
1
+ /* Shared material. Public variables can be set once on a theme container. */
2
+ :where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
3
+ --_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
4
+ --_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
5
+ }
6
+
7
+ :where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
8
+ :where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
9
+
10
+
11
+ .opal-glass { position:relative; isolation:isolate; border-radius:28px; padding:28px; color:var(--opal-glass-color,inherit); }
12
+ .opal-glass::before { content:''; position:absolute; inset:0; z-index:-1; border-radius:inherit; pointer-events:none; background:var(--opal-glass-background,rgb(255 255 255 / 22%)); -webkit-backdrop-filter:blur(var(--opal-material-frost,2px)); backdrop-filter:var(--opal-material-filter,blur(var(--opal-material-frost,2px))); box-shadow:var(--_opal-glass-shadow); }
13
+ @media(prefers-reduced-transparency:reduce) { .opal-glass::before { background:var(--opal-glass-solid,#f5f5f5); backdrop-filter:none; -webkit-backdrop-filter:none; } .opal-glass { color:var(--opal-glass-solid-color,#202020); } }
14
+ @media(forced-colors:active) { .opal-glass { border:1px solid CanvasText; color:CanvasText; } .opal-glass::before { background:Canvas; backdrop-filter:none; box-shadow:none; } }
15
+
16
+ .opal-glass::after { content:''; position:absolute; inset:0; border-radius:inherit; pointer-events:none; box-shadow:var(--opal-glass-rim,inset 0 0 0 .5px rgb(255 255 255 / 30%),inset .65px 1px .65px rgb(255 255 255 / 90%),inset -.4px -.65px .8px rgb(255 255 255 / 45%)); opacity:var(--opal-material-rim,.3); }
@@ -0,0 +1 @@
1
+ export default ".opal-glass { position:relative; isolation:isolate; border-radius:28px; padding:28px; color:var(--opal-glass-color,inherit); }\n.opal-glass::before { content:''; position:absolute; inset:0; z-index:-1; border-radius:inherit; pointer-events:none; background:var(--opal-glass-background,rgb(255 255 255 / 22%)); -webkit-backdrop-filter:blur(var(--opal-material-frost,2px)); backdrop-filter:var(--opal-material-filter,blur(var(--opal-material-frost,2px))); box-shadow:var(--_opal-glass-shadow); }\n@media(prefers-reduced-transparency:reduce) { .opal-glass::before { background:var(--opal-glass-solid,#f5f5f5); backdrop-filter:none; -webkit-backdrop-filter:none; } .opal-glass { color:var(--opal-glass-solid-color,#202020); } }\n@media(forced-colors:active) { .opal-glass { border:1px solid CanvasText; color:CanvasText; } .opal-glass::before { background:Canvas; backdrop-filter:none; box-shadow:none; } }\n\n.opal-glass::after { content:''; position:absolute; inset:0; border-radius:inherit; pointer-events:none; box-shadow:var(--opal-glass-rim,inset 0 0 0 .5px rgb(255 255 255 / 30%),inset .65px 1px .65px rgb(255 255 255 / 90%),inset -.4px -.65px .8px rgb(255 255 255 / 45%)); opacity:var(--opal-material-rim,.3); }\n";
@@ -0,0 +1,5 @@
1
+ import { type ComponentPropsWithoutRef } from 'react';
2
+ import { type GlassSettings } from '../../internal/glass/material.js';
3
+ export type GlassProps = ComponentPropsWithoutRef<'div'> & Partial<GlassSettings>;
4
+ /** A glass surface. Children remain sharp; only the backdrop is refracted. */
5
+ export declare const Glass: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & Partial<GlassSettings> & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useImperativeHandle, useRef } from 'react';
4
+ import { useGlassLens } from '../../internal/glass/lens.js';
5
+ import { materials, resolveMaterial } from '../../internal/glass/material.js';
6
+ import { stylesheetHref } from '../../internal/styles.js';
7
+ import sharedCss from '../../internal/glass/styles.js';
8
+ import componentCss from './glass.css.js';
9
+ const css = `${sharedCss}\n${componentCss}`;
10
+ const href = stylesheetHref('glass', css);
11
+ /** A glass surface. Children remain sharp; only the backdrop is refracted. */
12
+ export const Glass = forwardRef(function Glass({ bend, dispersion, frost, rim, className, style, children, ...props }, ref) {
13
+ const element = useRef(null);
14
+ useImperativeHandle(ref, () => element.current, []);
15
+ const material = resolveMaterial(materials.surface, { bend, dispersion, frost, rim });
16
+ const glass = useGlassLens(element, material);
17
+ return _jsxs(_Fragment, { children: [_jsx("style", { href: href, precedence: "opalui", children: css }), glass.filter, _jsx("div", { ...props, ref: element, className: ['opal-glass', className].filter(Boolean).join(' '), style: { ...glass.surfaceStyle, ...style }, children: children })] });
18
+ });
19
+ //# sourceMappingURL=glass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glass.js","sourceRoot":"","sources":["../../../src/components/glass/glass.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAiC,MAAM,OAAO,CAAA;AAC9F,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAsB,MAAM,kCAAkC,CAAA;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,SAAS,MAAM,gCAAgC,CAAA;AACtD,OAAO,YAAY,MAAM,oBAAoB,CAAA;AAC7C,MAAM,GAAG,GAAG,GAAG,SAAS,KAAK,YAAY,EAAE,CAAA;AAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;AAEzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA6B,SAAS,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG;IACpJ,MAAM,OAAO,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC5C,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAQ,EAAE,EAAE,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IACrF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC7C,OAAO,8BACL,gBAAO,IAAI,EAAE,IAAI,EAAE,UAAU,EAAC,QAAQ,YAAE,GAAG,GAAS,EACnD,KAAK,CAAC,MAAM,EACb,iBAAS,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,EAAE,YAAG,QAAQ,GAAO,IACzJ,CAAA;AACL,CAAC,CAAC,CAAA","sourcesContent":["'use client'\n\nimport { forwardRef, useImperativeHandle, useRef, type ComponentPropsWithoutRef } from 'react'\nimport { useGlassLens } from '../../internal/glass/lens.js'\nimport { materials, resolveMaterial, type GlassSettings } from '../../internal/glass/material.js'\nimport { stylesheetHref } from '../../internal/styles.js'\nimport sharedCss from '../../internal/glass/styles.js'\nimport componentCss from './glass.css?inline'\nconst css = `${sharedCss}\\n${componentCss}`\nconst href = stylesheetHref('glass', css)\nexport type GlassProps = ComponentPropsWithoutRef<'div'> & Partial<GlassSettings>\n/** A glass surface. Children remain sharp; only the backdrop is refracted. */\nexport const Glass = forwardRef<HTMLDivElement, GlassProps>(function Glass({ bend, dispersion, frost, rim, className, style, children, ...props }, ref) {\n const element = useRef<HTMLDivElement>(null)\n useImperativeHandle(ref, () => element.current!, [])\n const material = resolveMaterial(materials.surface, { bend, dispersion, frost, rim })\n const glass = useGlassLens(element, material)\n return <>\n <style href={href} precedence=\"opalui\">{css}</style>\n {glass.filter}\n <div {...props} ref={element} className={['opal-glass', className].filter(Boolean).join(' ')} style={{ ...glass.surfaceStyle, ...style }}>{children}</div>\n </>\n})\n"]}
@@ -0,0 +1,43 @@
1
+ /* Shared material. Public variables can be set once on a theme container. */
2
+ :where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
3
+ --_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
4
+ --_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
5
+ }
6
+
7
+ :where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
8
+ :where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
9
+
10
+
11
+ .opal-input {
12
+ position: relative;
13
+ display: inline-flex; align-items: center; gap: 10px; box-sizing: border-box;
14
+ width: 360px; max-width: 100%; min-height: 44px; padding: 0 15px; border-radius: 999px;
15
+ background: var(--opal-input-background, rgb(255 255 255 / 65%)); color: var(--opal-input-color, #171e22);
16
+ box-shadow: var(--opal-input-shadow, var(--_opal-glass-shadow));
17
+ backdrop-filter: blur(var(--opal-material-frost,12px)); -webkit-backdrop-filter: blur(var(--opal-material-frost,12px));
18
+ transition: background-color 180ms ease, box-shadow 180ms ease;
19
+ }
20
+ .opal-input::after {
21
+ content: ''; position: absolute; inset: 0; border-radius: inherit;
22
+ pointer-events: none;
23
+ box-shadow: var(--opal-input-rim, var(--_opal-glass-rim));
24
+ opacity: .7;
25
+ }
26
+ .opal-input:focus-within {
27
+ background: var(--opal-input-focus-background, rgb(255 255 255 / 90%));
28
+ }
29
+ .opal-input:has([aria-invalid="true"]) { outline: 2px solid #e5484d; outline-offset: 2px; }
30
+ .opal-input-field { width: 100%; min-width: 0; padding: 12px 0; border: 0; outline: 0; background: transparent; color: inherit; font: inherit; line-height: 1.25; }
31
+ .opal-input-field::placeholder { color: inherit; opacity: .5; }
32
+ .opal-input-field::-webkit-search-cancel-button, .opal-input-field::-webkit-search-decoration { -webkit-appearance: none; }
33
+ .opal-input-icon { display: inline-flex; width: 20px; height: 20px; flex: 0 0 20px; opacity: .55; pointer-events: none; }
34
+ .opal-input-icon > svg { width: 100%; height: 100%; }
35
+ .opal-input-clear { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 28px; width: 28px; height: 28px; margin-inline-end: -6px; padding: 4px; border: 0; border-radius: 50%; background: transparent; color: inherit; opacity: .55; cursor: pointer; }
36
+ .opal-input-clear svg { width: 20px; height: 20px; }
37
+ .opal-input-clear:hover { opacity: 1; }
38
+ .opal-input-clear:focus-visible { outline: 2px solid var(--opal-focus-color, #2885ff); outline-offset: 1px; }
39
+ .opal-input[data-disabled] { opacity: .4; cursor: not-allowed; }
40
+ .opal-input-field:disabled { cursor: not-allowed; }
41
+ @media (prefers-reduced-motion: reduce) { .opal-input { transition: none; } }
42
+ @media (prefers-reduced-transparency: reduce) { .opal-input { background: var(--opal-input-solid, #fff); backdrop-filter: none; -webkit-backdrop-filter: none; } }
43
+ @media (forced-colors: active) { .opal-input { border: 1px solid ButtonText; } }
@@ -0,0 +1 @@
1
+ export default ".opal-input {\n position: relative;\n display: inline-flex; align-items: center; gap: 10px; box-sizing: border-box;\n width: 360px; max-width: 100%; min-height: 44px; padding: 0 15px; border-radius: 999px;\n background: var(--opal-input-background, rgb(255 255 255 / 65%)); color: var(--opal-input-color, #171e22);\n box-shadow: var(--opal-input-shadow, var(--_opal-glass-shadow));\n backdrop-filter: blur(var(--opal-material-frost,12px)); -webkit-backdrop-filter: blur(var(--opal-material-frost,12px));\n transition: background-color 180ms ease, box-shadow 180ms ease;\n}\n.opal-input::after {\n content: ''; position: absolute; inset: 0; border-radius: inherit;\n pointer-events: none;\n box-shadow: var(--opal-input-rim, var(--_opal-glass-rim));\n opacity: .7;\n}\n.opal-input:focus-within {\n background: var(--opal-input-focus-background, rgb(255 255 255 / 90%));\n}\n.opal-input:has([aria-invalid=\"true\"]) { outline: 2px solid #e5484d; outline-offset: 2px; }\n.opal-input-field { width: 100%; min-width: 0; padding: 12px 0; border: 0; outline: 0; background: transparent; color: inherit; font: inherit; line-height: 1.25; }\n.opal-input-field::placeholder { color: inherit; opacity: .5; }\n.opal-input-field::-webkit-search-cancel-button, .opal-input-field::-webkit-search-decoration { -webkit-appearance: none; }\n.opal-input-icon { display: inline-flex; width: 20px; height: 20px; flex: 0 0 20px; opacity: .55; pointer-events: none; }\n.opal-input-icon > svg { width: 100%; height: 100%; }\n.opal-input-clear { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 28px; width: 28px; height: 28px; margin-inline-end: -6px; padding: 4px; border: 0; border-radius: 50%; background: transparent; color: inherit; opacity: .55; cursor: pointer; }\n.opal-input-clear svg { width: 20px; height: 20px; }\n.opal-input-clear:hover { opacity: 1; }\n.opal-input-clear:focus-visible { outline: 2px solid var(--opal-focus-color, #2885ff); outline-offset: 1px; }\n.opal-input[data-disabled] { opacity: .4; cursor: not-allowed; }\n.opal-input-field:disabled { cursor: not-allowed; }\n@media (prefers-reduced-motion: reduce) { .opal-input { transition: none; } }\n@media (prefers-reduced-transparency: reduce) { .opal-input { background: var(--opal-input-solid, #fff); backdrop-filter: none; -webkit-backdrop-filter: none; } }\n@media (forced-colors: active) { .opal-input { border: 1px solid ButtonText; } }\n";
@@ -0,0 +1,17 @@
1
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
2
+ export type InputProps = Omit<ComponentPropsWithoutRef<'input'>, 'children' | 'type'> & {
3
+ type?: 'text' | 'search' | 'email' | 'url' | 'tel' | 'password' | 'number';
4
+ icon?: ReactNode;
5
+ /** Defaults to true for search inputs. */
6
+ clearable?: boolean;
7
+ clearLabel?: string;
8
+ onValueChange?: (value: string) => void;
9
+ };
10
+ export declare const Input: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "type" | "children"> & {
11
+ type?: "text" | "search" | "email" | "url" | "tel" | "password" | "number";
12
+ icon?: ReactNode;
13
+ /** Defaults to true for search inputs. */
14
+ clearable?: boolean;
15
+ clearLabel?: string;
16
+ onValueChange?: (value: string) => void;
17
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
4
+ import { stylesheetHref } from '../../internal/styles.js';
5
+ import componentCss from './input.css.js';
6
+ import glassCss from '../../internal/glass/styles.js';
7
+ const css = `${glassCss}\n${componentCss}`;
8
+ const styleHref = stylesheetHref('input', css);
9
+ export const Input = forwardRef(function Input({ icon, className, style, type = 'text', clearable = type === 'search', clearLabel = 'Clear input', onChange, onValueChange, value, defaultValue, disabled, readOnly, ...props }, ref) {
10
+ const input = useRef(null);
11
+ useImperativeHandle(ref, () => input.current, []);
12
+ const [text, setText] = useState(String(value ?? defaultValue ?? ''));
13
+ const current = value === undefined ? text : String(value);
14
+ useEffect(() => {
15
+ const form = input.current?.form;
16
+ const reset = () => queueMicrotask(() => { if (input.current)
17
+ setText(input.current.value); });
18
+ form?.addEventListener('reset', reset);
19
+ return () => form?.removeEventListener('reset', reset);
20
+ }, []);
21
+ return _jsxs(_Fragment, { children: [_jsx("style", { href: styleHref, precedence: "opalui", children: css }), _jsxs("span", { className: ['opal-input', className].filter(Boolean).join(' '), style: style, "data-disabled": disabled || undefined, children: [icon && _jsx("span", { className: "opal-input-icon", "aria-hidden": "true", children: icon }), _jsx("input", { ...props, ref: input, type: type, value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, className: "opal-input-field", onChange: event => {
22
+ setText(event.currentTarget.value);
23
+ onChange?.(event);
24
+ if (!event.defaultPrevented)
25
+ onValueChange?.(event.currentTarget.value);
26
+ } }), clearable && current.length > 0 && !disabled && !readOnly && _jsx("button", { className: "opal-input-clear", type: "button", "aria-label": clearLabel, onClick: () => {
27
+ const field = input.current;
28
+ if (!field)
29
+ return;
30
+ // Use the native setter so React receives the same change as typing.
31
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(field, '');
32
+ field.dispatchEvent(new Event('input', { bubbles: true }));
33
+ field.focus({ preventScroll: true });
34
+ }, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "m7 7 10 10M17 7 7 17" }) }) })] })] });
35
+ });
36
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../../src/components/input/input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport { forwardRef, useEffect, useImperativeHandle, useRef, useState, type ComponentPropsWithoutRef, type ReactNode } from 'react'\nimport { stylesheetHref } from '../../internal/styles.js'\nimport componentCss from './input.css?inline'\nimport glassCss from '../../internal/glass/styles.js'\nconst css = `${glassCss}\\n${componentCss}`\nconst styleHref = stylesheetHref('input', css)\nexport type InputProps = Omit<ComponentPropsWithoutRef<'input'>, 'children' | 'type'> & {\n type?: 'text' | 'search' | 'email' | 'url' | 'tel' | 'password' | 'number'\n icon?: ReactNode\n /** Defaults to true for search inputs. */\n clearable?: boolean\n clearLabel?: string\n onValueChange?: (value: string) => void\n}\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n { icon, className, style, type = 'text', clearable = type === 'search', clearLabel = 'Clear input', onChange, onValueChange, value, defaultValue, disabled, readOnly, ...props }, ref,\n) {\n const input = useRef<HTMLInputElement>(null)\n useImperativeHandle(ref, () => input.current!, [])\n const [text, setText] = useState(String(value ?? defaultValue ?? ''))\n const current = value === undefined ? text : String(value)\n useEffect(() => {\n const form = input.current?.form\n const reset = () => queueMicrotask(() => { if (input.current) setText(input.current.value) })\n form?.addEventListener('reset', reset)\n return () => form?.removeEventListener('reset', reset)\n }, [])\n return <>\n <style href={styleHref} precedence=\"opalui\">{css}</style>\n <span className={['opal-input', className].filter(Boolean).join(' ')} style={style} data-disabled={disabled || undefined}>\n {icon && <span className=\"opal-input-icon\" aria-hidden=\"true\">{icon}</span>}\n <input {...props} ref={input} type={type} value={value} defaultValue={defaultValue} disabled={disabled} readOnly={readOnly}\n className=\"opal-input-field\" onChange={event => {\n setText(event.currentTarget.value)\n onChange?.(event)\n if (!event.defaultPrevented) onValueChange?.(event.currentTarget.value)\n }} />\n {clearable && current.length > 0 && !disabled && !readOnly && <button className=\"opal-input-clear\" type=\"button\" aria-label={clearLabel}\n onClick={() => {\n const field = input.current\n if (!field) return\n // Use the native setter so React receives the same change as typing.\n Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(field, '')\n field.dispatchEvent(new Event('input', { bubbles: true }))\n field.focus({ preventScroll: true })\n }}><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\"><path d=\"m7 7 10 10M17 7 7 17\" /></svg></button>}\n </span>\n </>\n})\n"]}
@@ -0,0 +1,85 @@
1
+ /* Shared material. Public variables can be set once on a theme container. */
2
+ :where(.opal-glass, .opal-button, .opal-input, .opal-menu-panel, .opal-switch, .opal-slider, .opal-segmented, .opal-textarea, .opal-select) {
3
+ --_opal-glass-rim: var(--opal-glass-rim, inset 0 0 0 .5px rgb(255 255 255 / 7%), inset .65px 1px .65px rgb(255 255 255 / var(--opal-material-rim, .28)), inset -.4px -.65px .8px rgb(255 255 255 / calc(var(--opal-material-rim, .28) * .43)));
4
+ --_opal-glass-shadow: var(--opal-glass-shadow, 0 3px 8px rgb(15 34 47 / 12%));
5
+ }
6
+
7
+ :where(.opal-input,.opal-textarea,.opal-select) { --opal-material-frost:12px; --opal-material-rim:0.22; }
8
+ :where(.opal-menu-panel) { --opal-material-frost:24px; --opal-material-rim:0.3; }
9
+
10
+
11
+ .opal-menu { display: inline-flex; vertical-align: middle; }
12
+ .opal-menu-panel {
13
+ display: none;
14
+ position: fixed;
15
+ inset: auto;
16
+ margin: 0;
17
+ padding: 6px;
18
+ box-sizing: border-box;
19
+ width: 228px;
20
+ max-width: calc(100vw - 16px);
21
+ max-height: calc(100dvh - 16px);
22
+ overflow: auto;
23
+ z-index: 1000;
24
+ border: 0;
25
+ border-radius: 22px;
26
+ color: var(--opal-menu-color, #202124);
27
+ background: var(--opal-menu-background, rgb(250 250 252 / 86%));
28
+ backdrop-filter: blur(var(--opal-material-frost,24px)) saturate(150%);
29
+ -webkit-backdrop-filter: blur(var(--opal-material-frost,24px)) saturate(150%);
30
+ box-shadow: 0 12px 36px rgb(0 0 0 / 18%), inset 0 0 0 .5px var(--opal-menu-edge, rgb(0 0 0 / 12%)), var(--_opal-glass-rim);
31
+ font: inherit;
32
+ outline: none;
33
+ opacity: 0;
34
+ transform: scale(.6);
35
+ transition: opacity 160ms ease, transform 300ms cubic-bezier(.2,.8,.2,1), display 300ms allow-discrete, overlay 300ms allow-discrete;
36
+ }
37
+ .opal-menu-panel[data-open] { display: block; opacity: 1; transform: scale(1); }
38
+ @starting-style { .opal-menu-panel[data-open] { opacity: 0; transform: scale(.6); } }
39
+ .opal-menu-highlight {
40
+ position: absolute;
41
+ top: 0;
42
+ inset-inline: 6px;
43
+ border-radius: 16px;
44
+ pointer-events: none;
45
+ background: var(--opal-menu-highlight, rgb(0 0 0 / 7%));
46
+ transition: transform 220ms cubic-bezier(.22,.61,.36,1), height 220ms cubic-bezier(.22,.61,.36,1);
47
+ }
48
+ .opal-menu-item {
49
+ position: relative;
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 12px;
53
+ width: 100%;
54
+ min-height: 38px;
55
+ padding: 8px 12px;
56
+ border: 0;
57
+ border-radius: 16px;
58
+ background: transparent;
59
+ color: inherit;
60
+ font: inherit;
61
+ font-size: 14px;
62
+ line-height: 1.35;
63
+ text-align: start;
64
+ cursor: pointer;
65
+ outline: none;
66
+ -webkit-tap-highlight-color: transparent;
67
+ transition: transform 160ms ease;
68
+ }
69
+ .opal-menu-item:active:not(:disabled) { transform: scale(.98); }
70
+ .opal-menu-item:focus-visible { box-shadow: inset 0 0 0 2px var(--opal-focus-color, #2885ff); }
71
+ .opal-menu-item[data-destructive] { color: var(--opal-menu-destructive, #d6293e); }
72
+ .opal-menu-item:disabled { opacity: .4; cursor: default; }
73
+ .opal-menu-icon { display: inline-flex; width: 18px; height: 18px; flex: 0 0 18px; }
74
+ .opal-menu-icon > svg { width: 100%; height: 100%; }
75
+ .opal-menu-separator { height: 1px; margin: 6px 10px; background: var(--opal-menu-edge, rgb(0 0 0 / 10%)); }
76
+ @media (prefers-reduced-motion: reduce) { .opal-menu-panel, .opal-menu-item { transition: none; transform: none; } }
77
+ @media (prefers-reduced-motion: reduce) { .opal-menu-highlight { transition: none; } }
78
+ @media (prefers-reduced-transparency: reduce) { .opal-menu-panel { background: var(--opal-menu-solid, #fafafa); backdrop-filter: none; -webkit-backdrop-filter: none; } }
79
+ @media (forced-colors: active) {
80
+ .opal-menu-panel { background: Canvas; color: CanvasText; border: 1px solid ButtonText; }
81
+ .opal-menu-item:focus { outline: 2px solid Highlight; }
82
+ .opal-menu-highlight { background: transparent; }
83
+ .opal-menu-item[data-destructive] { color: ButtonText; }
84
+ .opal-menu-separator { background: ButtonText; }
85
+ }
@@ -0,0 +1 @@
1
+ export default ".opal-menu { display: inline-flex; vertical-align: middle; }\n.opal-menu-panel {\n display: none;\n position: fixed;\n inset: auto;\n margin: 0;\n padding: 6px;\n box-sizing: border-box;\n width: 228px;\n max-width: calc(100vw - 16px);\n max-height: calc(100dvh - 16px);\n overflow: auto;\n z-index: 1000;\n border: 0;\n border-radius: 22px;\n color: var(--opal-menu-color, #202124);\n background: var(--opal-menu-background, rgb(250 250 252 / 86%));\n backdrop-filter: blur(var(--opal-material-frost,24px)) saturate(150%);\n -webkit-backdrop-filter: blur(var(--opal-material-frost,24px)) saturate(150%);\n box-shadow: 0 12px 36px rgb(0 0 0 / 18%), inset 0 0 0 .5px var(--opal-menu-edge, rgb(0 0 0 / 12%)), var(--_opal-glass-rim);\n font: inherit;\n outline: none;\n opacity: 0;\n transform: scale(.6);\n transition: opacity 160ms ease, transform 300ms cubic-bezier(.2,.8,.2,1), display 300ms allow-discrete, overlay 300ms allow-discrete;\n}\n.opal-menu-panel[data-open] { display: block; opacity: 1; transform: scale(1); }\n@starting-style { .opal-menu-panel[data-open] { opacity: 0; transform: scale(.6); } }\n.opal-menu-highlight {\n position: absolute;\n top: 0;\n inset-inline: 6px;\n border-radius: 16px;\n pointer-events: none;\n background: var(--opal-menu-highlight, rgb(0 0 0 / 7%));\n transition: transform 220ms cubic-bezier(.22,.61,.36,1), height 220ms cubic-bezier(.22,.61,.36,1);\n}\n.opal-menu-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n width: 100%;\n min-height: 38px;\n padding: 8px 12px;\n border: 0;\n border-radius: 16px;\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 14px;\n line-height: 1.35;\n text-align: start;\n cursor: pointer;\n outline: none;\n -webkit-tap-highlight-color: transparent;\n transition: transform 160ms ease;\n}\n.opal-menu-item:active:not(:disabled) { transform: scale(.98); }\n.opal-menu-item:focus-visible { box-shadow: inset 0 0 0 2px var(--opal-focus-color, #2885ff); }\n.opal-menu-item[data-destructive] { color: var(--opal-menu-destructive, #d6293e); }\n.opal-menu-item:disabled { opacity: .4; cursor: default; }\n.opal-menu-icon { display: inline-flex; width: 18px; height: 18px; flex: 0 0 18px; }\n.opal-menu-icon > svg { width: 100%; height: 100%; }\n.opal-menu-separator { height: 1px; margin: 6px 10px; background: var(--opal-menu-edge, rgb(0 0 0 / 10%)); }\n@media (prefers-reduced-motion: reduce) { .opal-menu-panel, .opal-menu-item { transition: none; transform: none; } }\n@media (prefers-reduced-motion: reduce) { .opal-menu-highlight { transition: none; } }\n@media (prefers-reduced-transparency: reduce) { .opal-menu-panel { background: var(--opal-menu-solid, #fafafa); backdrop-filter: none; -webkit-backdrop-filter: none; } }\n@media (forced-colors: active) {\n .opal-menu-panel { background: Canvas; color: CanvasText; border: 1px solid ButtonText; }\n .opal-menu-item:focus { outline: 2px solid Highlight; }\n .opal-menu-highlight { background: transparent; }\n .opal-menu-item[data-destructive] { color: ButtonText; }\n .opal-menu-separator { background: ButtonText; }\n}\n";
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ export type MenuItem = {
3
+ label: string;
4
+ icon?: ReactNode;
5
+ onSelect?: () => void;
6
+ disabled?: boolean;
7
+ destructive?: boolean;
8
+ } | 'separator';
9
+ export type MenuProps = {
10
+ 'aria-label': string;
11
+ items: readonly MenuItem[];
12
+ icon?: ReactNode;
13
+ children?: ReactNode;
14
+ disabled?: boolean;
15
+ className?: string;
16
+ };
17
+ /** A compact action menu. Styles are included automatically. */
18
+ export declare function Menu({ items, icon, children, disabled, className, 'aria-label': label }: MenuProps): import("react").JSX.Element;