@djangocfg/ui-core 2.1.411 → 2.1.413

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 (52) hide show
  1. package/package.json +4 -4
  2. package/src/components/data/avatar-group/index.tsx +224 -0
  3. package/src/components/data/badge-overflow/index.tsx +259 -0
  4. package/src/components/data/circular-progress/index.tsx +358 -0
  5. package/src/components/data/relative-time-card/index.tsx +191 -0
  6. package/src/components/data/stat/index.tsx +140 -0
  7. package/src/components/data/status/index.tsx +80 -0
  8. package/src/components/effects/GlowBackground.tsx +9 -1
  9. package/src/components/effects/swap/index.tsx +289 -0
  10. package/src/components/feedback/banner/index.tsx +693 -0
  11. package/src/components/forms/checkbox-group/index.tsx +243 -0
  12. package/src/components/forms/editable/index.tsx +420 -0
  13. package/src/components/forms/input-otp/index.tsx +12 -3
  14. package/src/components/forms/mask-input/index.tsx +466 -0
  15. package/src/components/forms/otp/index.tsx +12 -8
  16. package/src/components/forms/segmented-input/index.tsx +319 -0
  17. package/src/components/forms/tags-input/index.tsx +896 -0
  18. package/src/components/forms/time-picker/index.tsx +285 -0
  19. package/src/components/index.ts +51 -0
  20. package/src/components/layout/key-value/index.tsx +884 -0
  21. package/src/components/layout/stack/index.tsx +349 -0
  22. package/src/components/navigation/context-menu/index.tsx +9 -6
  23. package/src/components/navigation/stepper/index.tsx +1307 -0
  24. package/src/components/select/multi-select-pro-async.tsx +11 -2
  25. package/src/components/select/multi-select-pro.tsx +11 -2
  26. package/src/components/select/select.tsx +13 -3
  27. package/src/components/specialized/presence/index.tsx +181 -0
  28. package/src/components/specialized/primitive/index.tsx +83 -0
  29. package/src/components/specialized/visually-hidden/index.tsx +19 -0
  30. package/src/components/specialized/visually-hidden-input/index.tsx +99 -0
  31. package/src/hooks/dom/index.ts +4 -0
  32. package/src/hooks/dom/useFormReset.ts +49 -0
  33. package/src/hooks/dom/useLayoutEffect.ts +16 -0
  34. package/src/hooks/dom/useSize.ts +57 -0
  35. package/src/hooks/state/index.ts +4 -0
  36. package/src/hooks/state/useCallbackRef.ts +25 -0
  37. package/src/hooks/state/usePrevious.ts +20 -0
  38. package/src/hooks/state/useStateMachine.ts +29 -0
  39. package/src/lib/compose-event-handlers.ts +22 -0
  40. package/src/lib/compose-refs.ts +65 -0
  41. package/src/lib/create-context.tsx +62 -0
  42. package/src/lib/get-element-ref.ts +33 -0
  43. package/src/lib/index.ts +5 -0
  44. package/src/lib/styles.ts +103 -0
  45. package/src/styles/README.md +43 -0
  46. package/src/styles/palette/utils.ts +15 -5
  47. package/src/styles/utilities/animations.css +135 -0
  48. package/src/styles/utilities/display.css +62 -0
  49. package/src/styles/utilities/glass.css +57 -0
  50. package/src/styles/utilities/marquee.css +69 -0
  51. package/src/styles/utilities/step.css +25 -0
  52. package/src/styles/utilities.css +6 -259
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ interface StateMachineConfig<TState extends string, TEvent extends string> {
6
+ initial: TState;
7
+ states: Record<TState, Partial<Record<TEvent, TState>>>;
8
+ }
9
+
10
+ function useStateMachine<TState extends string, TEvent extends string>(
11
+ config: StateMachineConfig<TState, TEvent>,
12
+ ) {
13
+ const [state, setState] = React.useState<TState>(config.initial);
14
+
15
+ const send = React.useCallback(
16
+ (event: TEvent) => {
17
+ setState((currentState) => {
18
+ const transition = config.states[currentState]?.[event];
19
+ return transition ?? currentState;
20
+ });
21
+ },
22
+ [config.states],
23
+ );
24
+
25
+ return [state, send] as const;
26
+ }
27
+
28
+ export { useStateMachine };
29
+ export type { StateMachineConfig };
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ function composeEventHandlers<E>(
4
+ originalEventHandler?: (event: E) => void,
5
+ ourEventHandler?: (event: E) => void,
6
+ { checkForDefaultPrevented = true } = {},
7
+ ) {
8
+ return function handleEvent(event: E) {
9
+ originalEventHandler?.(event);
10
+
11
+ if (
12
+ checkForDefaultPrevented &&
13
+ (event as unknown as Event).defaultPrevented
14
+ ) {
15
+ return;
16
+ }
17
+
18
+ ourEventHandler?.(event);
19
+ };
20
+ }
21
+
22
+ export { composeEventHandlers };
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ type PossibleRef<T> = React.Ref<T> | undefined;
6
+
7
+ /**
8
+ * Set a given ref to a given value.
9
+ * This utility takes care of different types of refs: callback refs and RefObject(s).
10
+ */
11
+ function setRef<T>(ref: PossibleRef<T>, value: T) {
12
+ if (typeof ref === "function") {
13
+ return ref(value);
14
+ }
15
+
16
+ if (ref !== null && ref !== undefined) {
17
+ ref.current = value;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * A utility to compose multiple refs together.
23
+ * Accepts callback refs and RefObject(s).
24
+ */
25
+ function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
26
+ return (node) => {
27
+ let hasCleanup = false;
28
+ const cleanups = refs.map((ref) => {
29
+ const cleanup = setRef(ref, node);
30
+ if (!hasCleanup && typeof cleanup === "function") {
31
+ hasCleanup = true;
32
+ }
33
+ return cleanup;
34
+ });
35
+
36
+ // React <19 will log an error to the console if a callback ref returns a
37
+ // value. We don't use ref cleanups internally so this will only happen if a
38
+ // user's ref callback returns a value, which we only expect if they are
39
+ // using the cleanup functionality added in React 19.
40
+ if (hasCleanup) {
41
+ return () => {
42
+ for (let i = 0; i < cleanups.length; i++) {
43
+ const cleanup = cleanups[i];
44
+ if (typeof cleanup === "function") {
45
+ cleanup();
46
+ } else {
47
+ setRef(refs[i], null);
48
+ }
49
+ }
50
+ };
51
+ }
52
+ };
53
+ }
54
+
55
+ /**
56
+ * A custom hook that composes multiple refs.
57
+ * Accepts callback refs and RefObject(s).
58
+ */
59
+ function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ return React.useCallback(composeRefs(...refs), refs);
62
+ }
63
+
64
+ export { composeRefs, useComposedRefs };
65
+ export type { PossibleRef };
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /**
6
+ * Creates a type-safe context and provider with automatic error handling
7
+ * @template T The type of the context value
8
+ * @param name The name of the root component for error messages
9
+ * @param defaultValue Optional default value for the context
10
+ */
11
+ function createContext<T extends object | null>(
12
+ rootComponentName: string,
13
+ defaultValue?: T,
14
+ ) {
15
+ const Context = React.createContext<T | undefined>(defaultValue);
16
+ Context.displayName = rootComponentName;
17
+
18
+ function Provider(props: T & { children: React.ReactNode }) {
19
+ const { children, ...contextValue } = props;
20
+
21
+ // Memoize the context value by its values
22
+ const value = React.useMemo(
23
+ () => contextValue,
24
+ // eslint-disable-next-line react-hooks/exhaustive-deps
25
+ Object.values(contextValue),
26
+ ) as T;
27
+
28
+ return <Context.Provider value={value}>{children}</Context.Provider>;
29
+ }
30
+
31
+ Provider.displayName = `${rootComponentName}Provider`;
32
+
33
+ type ContextReturn<Optional extends boolean> = Optional extends true
34
+ ? T | undefined
35
+ : T;
36
+
37
+ /**
38
+ * @param consumerName The name of the component that is consuming the context
39
+ * @param optional Whether the context is optional (defaults to false)
40
+ */
41
+ function useContext<Optional extends boolean = false>(
42
+ consumerName: string,
43
+ optional?: Optional,
44
+ ): ContextReturn<Optional> {
45
+ const context = React.useContext(Context);
46
+
47
+ if (!context && !optional) {
48
+ throw new Error(
49
+ `\`${consumerName}\` must be used within \`${rootComponentName}\``,
50
+ );
51
+ }
52
+
53
+ if (context) return context;
54
+ if (defaultValue !== undefined) return defaultValue;
55
+
56
+ return undefined as ContextReturn<Optional>;
57
+ }
58
+
59
+ return [Provider, useContext] as const;
60
+ }
61
+
62
+ export { createContext };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ /**
6
+ * Get the ref from a React element without throwing warnings.
7
+ */
8
+ function getElementRef(element: React.ReactElement) {
9
+ if (!React.isValidElement(element)) return undefined;
10
+
11
+ // React <=18 in DEV
12
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
13
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
14
+ if (mayWarn) {
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ return (element as any).ref;
17
+ }
18
+
19
+ // React 19 in DEV
20
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
21
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
22
+ if (mayWarn) {
23
+ return (element.props as { ref?: React.Ref<unknown> }).ref;
24
+ }
25
+
26
+ // Not DEV
27
+ return (
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ (element.props as { ref?: React.Ref<unknown> }).ref || (element as any).ref
30
+ );
31
+ }
32
+
33
+ export { getElementRef };
package/src/lib/index.ts CHANGED
@@ -10,3 +10,8 @@ export type {
10
10
  CustomJsonUiGroup,
11
11
  CustomJsonUiDisabledWhenRule,
12
12
  } from "./configurator-schema";
13
+ export * from "./styles";
14
+ export * from "./compose-refs";
15
+ export * from "./get-element-ref";
16
+ export * from "./compose-event-handlers";
17
+ export { createContext } from "./create-context";
@@ -0,0 +1,103 @@
1
+ import type * as React from "react";
2
+
3
+ /**
4
+ * CSS style object that visually hides content while keeping it accessible to screen readers.
5
+ * This follows accessibility best practices for visually hidden content.
6
+ */
7
+ const visuallyHidden: React.CSSProperties = {
8
+ border: 0,
9
+ clip: "rect(0 0 0 0)",
10
+ clipPath: "inset(50%)",
11
+ height: "1px",
12
+ margin: "-1px",
13
+ overflow: "hidden",
14
+ padding: 0,
15
+ position: "absolute",
16
+ whiteSpace: "nowrap",
17
+ width: "1px",
18
+ } as const;
19
+
20
+ /**
21
+ * CSS style object for a full-screen overlay backdrop.
22
+ * Useful for modals, dialogs, and other overlay components.
23
+ */
24
+ const overlay: React.CSSProperties = {
25
+ position: "fixed",
26
+ inset: 0,
27
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
28
+ backdropFilter: "blur(2px)",
29
+ zIndex: 50,
30
+ } as const;
31
+
32
+ /**
33
+ * CSS style object for text truncation with ellipsis.
34
+ * Ensures text fits within its container.
35
+ */
36
+ const truncate: React.CSSProperties = {
37
+ overflow: "hidden",
38
+ textOverflow: "ellipsis",
39
+ whiteSpace: "nowrap",
40
+ } as const;
41
+
42
+ /**
43
+ * CSS style object for a custom focus ring style.
44
+ * Provides a consistent, accessible focus indicator.
45
+ */
46
+ const focusRing: React.CSSProperties = {
47
+ outline: "none",
48
+ boxShadow: "0 0 0 2px rgba(66, 153, 225, 0.6)",
49
+ borderRadius: "0.25rem",
50
+ } as const;
51
+
52
+ /**
53
+ * CSS style object to create a scrollable container that hides scrollbars.
54
+ * Note: For webkit browsers, you'll need to add additional CSS for ::-webkit-scrollbar
55
+ */
56
+ const scrollableHidden: React.CSSProperties = {
57
+ overflow: "auto",
58
+ scrollbarWidth: "none",
59
+ msOverflowStyle: "none",
60
+ } as const;
61
+
62
+ /**
63
+ * CSS style object for absolutely positioning an element to fill its container.
64
+ * Useful for overlays, backgrounds, and full-size children.
65
+ */
66
+ const fullSize: React.CSSProperties = {
67
+ position: "absolute",
68
+ top: 0,
69
+ right: 0,
70
+ bottom: 0,
71
+ left: 0,
72
+ } as const;
73
+
74
+ /**
75
+ * CSS style object for centering content both vertically and horizontally.
76
+ * Uses flexbox for better browser support.
77
+ */
78
+ const center: React.CSSProperties = {
79
+ display: "flex",
80
+ alignItems: "center",
81
+ justifyContent: "center",
82
+ } as const;
83
+
84
+ /**
85
+ * CSS style object to prevent text selection.
86
+ * Useful for interactive elements like buttons.
87
+ */
88
+ const noSelect: React.CSSProperties = {
89
+ userSelect: "none",
90
+ WebkitUserSelect: "none",
91
+ msUserSelect: "none",
92
+ } as const;
93
+
94
+ export {
95
+ center,
96
+ focusRing,
97
+ fullSize,
98
+ noSelect,
99
+ overlay,
100
+ scrollableHidden,
101
+ truncate,
102
+ visuallyHidden,
103
+ };
@@ -175,6 +175,49 @@ In your consuming app's `globals.css`:
175
175
 
176
176
  Tailwind v4 doesn't scan across npm packages automatically. Each consumer needs either a `@source` directive or to import a `sources.css` from the package — `@djangocfg/ui-core/styles` already chains its own `sources.css`, so importing `…/styles` is enough.
177
177
 
178
+ ### Wiring a custom display font (consumer recipe)
179
+
180
+ `ui-core` ships `.font-display` and a display type ramp (`.text-display-xl` / `.text-display-lg` / `.text-display`) that read from a `--font-display` CSS variable. The variable itself is **not** set — apps pick the font.
181
+
182
+ In a Next.js app with `next/font`:
183
+
184
+ ```tsx
185
+ // app/layout.tsx
186
+ import { Plus_Jakarta_Sans } from 'next/font/google';
187
+
188
+ const display = Plus_Jakarta_Sans({
189
+ subsets: ['latin'],
190
+ weight: ['700', '800'],
191
+ variable: '--font-display', // exposes as CSS var
192
+ display: 'swap',
193
+ });
194
+
195
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
196
+ return (
197
+ <html lang="en" className={display.variable}>
198
+ <body>{children}</body>
199
+ </html>
200
+ );
201
+ }
202
+ ```
203
+
204
+ Then in `globals.css` register it as a Tailwind token so the `font-display` utility class works:
205
+
206
+ ```css
207
+ @theme {
208
+ --font-display: var(--font-display), ui-sans-serif, system-ui, sans-serif;
209
+ }
210
+ ```
211
+
212
+ Now any of these work:
213
+
214
+ ```tsx
215
+ <h1 className="text-7xl text-display-xl">Hero title</h1>
216
+ <h2 className="text-4xl text-display-lg">Section title</h2>
217
+ <h3 className="text-xl text-display">Card title</h3>
218
+ <span className="font-display">Plain display family</span>
219
+ ```
220
+
178
221
  ## Theme Showcase story
179
222
 
180
223
  The `UI Core/Theme Showcase` story in djangocfg storybook renders every base token, status surface, button variant, card, form control, glass utility, and opacity sanity-check on one page. Switch the `preset` control to flip across all 8 themes; flip light/dark from the toolbar.
@@ -4,15 +4,25 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Parse HSL string from CSS variable (e.g., "192 90% 35%" for light `--primary`) to components
7
+ * Parse HSL string from CSS variable to components.
8
+ * Accepts either the raw triplet form `"192 90% 35%"` or the wrapped
9
+ * form `"hsl(192 90% 35%)"` / `"hsl(192, 90%, 35%)"`. `getComputedStyle`
10
+ * returns the wrapped form when the CSS variable is declared as
11
+ * `--primary: hsl(...)` rather than `--primary: 192 90% 35%`.
8
12
  */
9
13
  function parseHslString(hsl: string): { h: number; s: number; l: number } | null {
10
- const match = hsl.trim().match(/^(\d+)\s+(\d+)%\s+(\d+)%$/);
14
+ const stripped = hsl
15
+ .trim()
16
+ .replace(/^hsla?\(/i, '')
17
+ .replace(/\)$/, '')
18
+ .replace(/,/g, ' ')
19
+ .replace(/\s*\/\s*[\d.]+%?$/, ''); // drop optional `/ alpha`
20
+ const match = stripped.match(/^(-?\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%$/);
11
21
  if (!match) return null;
12
22
  return {
13
- h: parseInt(match[1], 10),
14
- s: parseInt(match[2], 10),
15
- l: parseInt(match[3], 10),
23
+ h: parseFloat(match[1]),
24
+ s: parseFloat(match[2]),
25
+ l: parseFloat(match[3]),
16
26
  };
17
27
  }
18
28
 
@@ -0,0 +1,135 @@
1
+ /* ============================================================================
2
+ * MultiSelectPro Animation Classes
3
+ * ============================================================================ */
4
+
5
+ @keyframes wiggle {
6
+ 0%, 100% { transform: rotate(-3deg); }
7
+ 50% { transform: rotate(3deg); }
8
+ }
9
+
10
+ .animate-wiggle {
11
+ animation: wiggle 0.5s ease-in-out;
12
+ }
13
+
14
+ @keyframes fadeIn {
15
+ from { opacity: 0; }
16
+ to { opacity: 1; }
17
+ }
18
+
19
+ .animate-fadeIn {
20
+ animation: fadeIn 0.3s ease-in-out;
21
+ }
22
+
23
+ @keyframes slideIn {
24
+ from {
25
+ opacity: 0;
26
+ transform: translateY(-10px);
27
+ }
28
+ to {
29
+ opacity: 1;
30
+ transform: translateY(0);
31
+ }
32
+ }
33
+
34
+ .animate-slideIn {
35
+ animation: slideIn 0.3s ease-out;
36
+ }
37
+
38
+ @keyframes scaleIn {
39
+ from {
40
+ opacity: 0;
41
+ transform: scale(0.95);
42
+ }
43
+ to {
44
+ opacity: 1;
45
+ transform: scale(1);
46
+ }
47
+ }
48
+
49
+ .animate-scaleIn {
50
+ animation: scaleIn 0.2s ease-out;
51
+ }
52
+
53
+ @keyframes scaleOut {
54
+ from {
55
+ opacity: 1;
56
+ transform: scale(1);
57
+ }
58
+ to {
59
+ opacity: 0;
60
+ transform: scale(0.95);
61
+ }
62
+ }
63
+
64
+ .animate-scaleOut {
65
+ animation: scaleOut 0.2s ease-in;
66
+ }
67
+
68
+ @keyframes slideDown {
69
+ from {
70
+ opacity: 0;
71
+ transform: translateY(-10px);
72
+ }
73
+ to {
74
+ opacity: 1;
75
+ transform: translateY(0);
76
+ }
77
+ }
78
+
79
+ .animate-slideDown {
80
+ animation: slideDown 0.3s ease-out;
81
+ }
82
+
83
+ @keyframes slideUp {
84
+ from {
85
+ opacity: 1;
86
+ transform: translateY(0);
87
+ }
88
+ to {
89
+ opacity: 0;
90
+ transform: translateY(-10px);
91
+ }
92
+ }
93
+
94
+ .animate-slideUp {
95
+ animation: slideUp 0.3s ease-in;
96
+ }
97
+
98
+ @keyframes fadeOut {
99
+ from { opacity: 1; }
100
+ to { opacity: 0; }
101
+ }
102
+
103
+ .animate-fadeOut {
104
+ animation: fadeOut 0.2s ease-in;
105
+ }
106
+
107
+ @keyframes flipIn {
108
+ from {
109
+ opacity: 0;
110
+ transform: rotateX(-90deg);
111
+ }
112
+ to {
113
+ opacity: 1;
114
+ transform: rotateX(0);
115
+ }
116
+ }
117
+
118
+ .animate-flipIn {
119
+ animation: flipIn 0.3s ease-out;
120
+ }
121
+
122
+ @keyframes flipOut {
123
+ from {
124
+ opacity: 1;
125
+ transform: rotateX(0);
126
+ }
127
+ to {
128
+ opacity: 0;
129
+ transform: rotateX(90deg);
130
+ }
131
+ }
132
+
133
+ .animate-flipOut {
134
+ animation: flipOut 0.3s ease-in;
135
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Display font utility — for marketing headlines and large numeric stats.
3
+ *
4
+ * The font itself is NOT shipped here. Apps pick their own display font and
5
+ * expose it as `--font-display` on <html> (via `next/font`, `@font-face`, or
6
+ * equivalent). If the variable is missing, falls back to the system UI stack.
7
+ */
8
+ .font-display {
9
+ font-family: var(--font-display), system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
10
+ font-feature-settings: 'ss01', 'ss03';
11
+ letter-spacing: -0.035em;
12
+ padding-bottom: 0.08em;
13
+ }
14
+
15
+ /**
16
+ * Display type ramp — opinionated headline presets that pair with
17
+ * `--font-display`. Use these instead of `.font-display` when you also
18
+ * want a tuned weight + tracking + line-height + balanced wrapping for
19
+ * a specific role:
20
+ *
21
+ * .text-display-xl hero (text-5xl / 6xl / 7xl) — 800, very tight
22
+ * .text-display-lg section title (text-3xl / 4xl) — 800, tight
23
+ * .text-display card / feature title (text-lg / xl) — 700, mild
24
+ *
25
+ * Size is intentionally NOT set here — pair with Tailwind `text-*`.
26
+ */
27
+ .text-display-xl {
28
+ font-family: var(--font-display), system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
29
+ font-weight: 800;
30
+ letter-spacing: -0.035em;
31
+ line-height: 1.02;
32
+ text-wrap: balance;
33
+ }
34
+ .text-display-lg {
35
+ font-family: var(--font-display), system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
36
+ font-weight: 800;
37
+ letter-spacing: -0.025em;
38
+ line-height: 1.05;
39
+ text-wrap: balance;
40
+ }
41
+ .text-display {
42
+ font-family: var(--font-display), system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
43
+ font-weight: 700;
44
+ letter-spacing: -0.015em;
45
+ line-height: 1.1;
46
+ }
47
+
48
+ /**
49
+ * Screen Reader Only
50
+ * Hides content visually but keeps it accessible to screen readers
51
+ */
52
+ .sr-only {
53
+ position: absolute;
54
+ width: 1px;
55
+ height: 1px;
56
+ padding: 0;
57
+ margin: -1px;
58
+ overflow: hidden;
59
+ clip: rect(0, 0, 0, 0);
60
+ white-space: nowrap;
61
+ border-width: 0;
62
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Glass surface utilities — OS-native translucency effects.
3
+ * Apply to panels/sidebars that sit over a blurred background.
4
+ * Requires `isolation: isolate` and a background image/color on a parent.
5
+ */
6
+
7
+ /**
8
+ * macOS Vibrancy / Liquid Glass — strong blur + high saturation over a
9
+ * 72% translucent base. The blur+saturate combo is the Apple-standard
10
+ * recipe (Sequoia / Tahoe 26 use the same values). 72% opacity keeps
11
+ * enough background bleed-through to read content underneath.
12
+ *
13
+ * Use on: sidebars, popovers, sheet headers, command palette overlays.
14
+ * Requires: container parent must be non-transparent (otherwise nothing
15
+ * to blur). Avoid stacking multiple `.glass-macos` — blur compounds.
16
+ */
17
+ .glass-macos {
18
+ backdrop-filter: blur(20px) saturate(180%);
19
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
20
+ background-color: color-mix(in oklab, var(--background) 72%, transparent);
21
+ }
22
+
23
+ /**
24
+ * Windows 11 Mica / Acrylic — wider/softer blur over an 85% base.
25
+ * Mica = wallpaper-tinted material; Acrylic = stronger blur on top.
26
+ * This recipe targets Acrylic (the closer-to-Apple flavour).
27
+ */
28
+ .glass-win11 {
29
+ backdrop-filter: blur(60px) saturate(125%);
30
+ -webkit-backdrop-filter: blur(60px) saturate(125%);
31
+ background-color: color-mix(in oklab, var(--background) 85%, transparent);
32
+ }
33
+
34
+ /**
35
+ * Liquid Glass variants — Apple's macOS 26 / iOS 26 design language.
36
+ * Thinner blur, brighter base, subtle inner highlight via box-shadow.
37
+ * Designed for floating chrome (Dock, navbar, FAB chips).
38
+ */
39
+ .glass-liquid {
40
+ backdrop-filter: blur(12px) saturate(180%);
41
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
42
+ background-color: color-mix(in oklab, var(--card) 60%, transparent);
43
+ border: 1px solid color-mix(in oklab, var(--foreground) 8%, transparent);
44
+ box-shadow:
45
+ 0 8px 32px color-mix(in oklab, var(--foreground) 12%, transparent),
46
+ inset 0 1px 0 color-mix(in oklab, var(--foreground) 6%, transparent);
47
+ }
48
+
49
+ /**
50
+ * Lighter glass for headers / status bars where strong blur would be
51
+ * too heavy. 8px blur is the macOS NavBar / TitleBar default.
52
+ */
53
+ .glass-header {
54
+ backdrop-filter: blur(8px) saturate(160%);
55
+ -webkit-backdrop-filter: blur(8px) saturate(160%);
56
+ background-color: color-mix(in oklab, var(--background) 80%, transparent);
57
+ }