@wallarm-org/design-system 1.22.3-rc-fix-date-utils-honour-format-context.1 → 1.23.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.
@@ -12,5 +12,10 @@ export interface AppShellProps extends HTMLAttributes<HTMLDivElement>, TestableP
12
12
  expandFrom?: AppShellExpandFrom;
13
13
  onRevealed?: () => void;
14
14
  appeared?: boolean;
15
+ /**
16
+ * The Frame's ambient layer — drifting blooms, grain and a dot cluster behind the header and
17
+ * rail. On by default; `false` leaves the flat Frame token fill.
18
+ */
19
+ ambient?: boolean;
15
20
  }
16
21
  export declare const AppShell: FC<AppShellProps>;
@@ -3,11 +3,12 @@ import { useMemo, useRef } from "react";
3
3
  import { composeRefs } from "@radix-ui/react-compose-refs";
4
4
  import { cn } from "../../utils/cn.js";
5
5
  import { TestIdProvider } from "../../utils/testId.js";
6
+ import { AppShellAmbient } from "./AppShellAmbient.js";
6
7
  import { AppShellContext } from "./AppShellContext.js";
7
8
  import { ExpandOverlay } from "./ExpandOverlay.js";
8
9
  import { RevealOverlay } from "./RevealOverlay.js";
9
10
  import { useShellAnimation } from "./useShellAnimation.js";
10
- const AppShell = ({ ref, className, children, reveal, expandFrom, onRevealed, appeared: appearedProp, 'data-testid': testId, ...props })=>{
11
+ const AppShell = ({ ref, className, children, reveal, expandFrom, onRevealed, appeared: appearedProp, ambient = true, 'data-testid': testId, ...props })=>{
11
12
  const internalRef = useRef(null);
12
13
  const { phase, targetRect, expandStyle, appeared, handleRevealOverlayTransitionEnd, handleExpandTransitionEnd } = useShellAnimation(internalRef, {
13
14
  reveal,
@@ -29,10 +30,11 @@ const AppShell = ({ ref, className, children, reveal, expandFrom, onRevealed, ap
29
30
  ref: composeRefs(internalRef, ref),
30
31
  "data-slot": "app-shell",
31
32
  "data-testid": testId,
32
- className: cn('relative grid h-screen overscroll-none [grid-template-areas:"header_header""rail_remote"] [grid-template-columns:auto_1fr] [grid-template-rows:auto_1fr] bg-component-app-shell-bg', className),
33
+ className: cn('relative isolate grid h-screen overscroll-none [grid-template-areas:"header_header""rail_remote"] [grid-template-columns:auto_1fr] [grid-template-rows:auto_1fr] bg-component-app-shell-bg', className),
33
34
  style: expandStyle,
34
35
  onTransitionEnd: expandFrom ? handleExpandTransitionEnd : void 0,
35
36
  children: [
37
+ ambient && /*#__PURE__*/ jsx(AppShellAmbient, {}),
36
38
  children,
37
39
  expandFrom && /*#__PURE__*/ jsx(ExpandOverlay, {
38
40
  phase: phase,
@@ -0,0 +1,10 @@
1
+ import type { FC } from 'react';
2
+ /**
3
+ * The Frame's ambient layer. The Frame is the shell's gray zone — the header
4
+ * bar plus the rail, everything around the canvas — and Ambient is the life in
5
+ * it: slow-drifting blooms, grain and a dot cluster pooled bottom-left of the
6
+ * rail. Purely decorative, painted under every region (see
7
+ * `theme/components/app-shell.css`). Internal to `AppShell`; toggled with its
8
+ * `ambient` prop.
9
+ */
10
+ export declare const AppShellAmbient: FC;
@@ -0,0 +1,42 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ const AppShellAmbient = ()=>{
4
+ const testId = useTestId('ambient');
5
+ return /*#__PURE__*/ jsxs("div", {
6
+ "aria-hidden": "true",
7
+ "data-slot": "app-shell-ambient",
8
+ "data-testid": testId,
9
+ className: "app-shell-ambient",
10
+ children: [
11
+ /*#__PURE__*/ jsx("div", {
12
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-1"
13
+ }),
14
+ /*#__PURE__*/ jsx("div", {
15
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-2"
16
+ }),
17
+ /*#__PURE__*/ jsx("div", {
18
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-3"
19
+ }),
20
+ /*#__PURE__*/ jsx("div", {
21
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-4"
22
+ }),
23
+ /*#__PURE__*/ jsx("div", {
24
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-5"
25
+ }),
26
+ /*#__PURE__*/ jsx("div", {
27
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-6"
28
+ }),
29
+ /*#__PURE__*/ jsx("div", {
30
+ className: "app-shell-ambient-bloom app-shell-ambient-bloom-7"
31
+ }),
32
+ /*#__PURE__*/ jsx("div", {
33
+ className: "app-shell-ambient-grain"
34
+ }),
35
+ /*#__PURE__*/ jsx("div", {
36
+ className: "app-shell-ambient-dots"
37
+ })
38
+ ]
39
+ });
40
+ };
41
+ AppShellAmbient.displayName = 'AppShellAmbient';
42
+ export { AppShellAmbient };
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.22.2",
3
- "generatedAt": "2026-09-24T08:10:05.373Z",
2
+ "version": "1.22.3",
3
+ "generatedAt": "2026-09-24T15:23:59.270Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -4232,6 +4232,13 @@
4232
4232
  "type": "boolean | undefined",
4233
4233
  "required": false
4234
4234
  },
4235
+ {
4236
+ "name": "ambient",
4237
+ "type": "boolean | undefined",
4238
+ "required": false,
4239
+ "description": "The Frame's ambient layer — drifting blooms, grain and a dot cluster behind the header and\nrail. On by default; `false` leaves the flat Frame token fill.",
4240
+ "defaultValue": "true"
4241
+ },
4235
4242
  {
4236
4243
  "name": "defaultChecked",
4237
4244
  "type": "boolean | undefined",
@@ -5334,7 +5341,7 @@
5334
5341
  "examples": [
5335
5342
  {
5336
5343
  "name": "Basic",
5337
- "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [loading, setLoading] = useState(true);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n return (\n <AppShell>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n {loading ? (\n <>\n <Skeleton width='150px' height='20px' rounded={6} />\n <TopHeaderSeparator />\n <Skeleton width='150px' height='20px' rounded={6} />\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n aria-label='Wallarm Updates'\n >\n <Bell />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Wallarm updates</TooltipContent>\n </Tooltip>\n\n <QuickHelpDropdown />\n </>\n ) : (\n <HeaderActions />\n )}\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n shortcut={['G', 'H']}\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n <RecentDropdown />\n\n <NavRailSeparator />\n\n {loading ? <NavRailSkeleton /> : <ProductNavItems activeProduct={activeProduct} />}\n </NavRailBody>\n\n <NavRailFooterContent\n activeProduct={activeProduct}\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRail>\n </AppShellRail>\n\n <AppShellRemote>\n <div className='flex gap-8 absolute top-4 right-4 z-10'>\n <Button variant='ghost' size='small' color='neutral' onClick={() => setLoading(v => !v)}>\n {loading ? 'Finish loading' : 'Start loading'}\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}",
5344
+ "code": "({ ambient }) => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [loading, setLoading] = useState(true);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n return (\n <AppShell ambient={ambient}>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n {loading ? (\n <>\n <Skeleton width='150px' height='20px' rounded={6} />\n <TopHeaderSeparator />\n <Skeleton width='150px' height='20px' rounded={6} />\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n aria-label='Wallarm Updates'\n >\n <Bell />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Wallarm updates</TooltipContent>\n </Tooltip>\n\n <QuickHelpDropdown />\n </>\n ) : (\n <HeaderActions />\n )}\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n shortcut={['G', 'H']}\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n <RecentDropdown />\n\n <NavRailSeparator />\n\n {loading ? <NavRailSkeleton /> : <ProductNavItems activeProduct={activeProduct} />}\n </NavRailBody>\n\n <NavRailFooterContent\n activeProduct={activeProduct}\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRail>\n </AppShellRail>\n\n <AppShellRemote>\n <div className='flex gap-8 absolute top-4 right-4 z-10'>\n <Button variant='ghost' size='small' color='neutral' onClick={() => setLoading(v => !v)}>\n {loading ? 'Finish loading' : 'Start loading'}\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}",
5338
5345
  "description": "The three regions composed once — `AppShellHeader`, `AppShellRail`, `AppShellRemote` — with the content surface being the only part that scrolls."
5339
5346
  },
5340
5347
  {
@@ -102156,6 +102163,10 @@
102156
102163
  "name": "--color-component-app-shell-bg",
102157
102164
  "value": "var(--color-slate-100)"
102158
102165
  },
102166
+ {
102167
+ "name": "--color-component-app-shell-ambient-dot",
102168
+ "value": "--alpha(var(--color-slate-500) / 28%)"
102169
+ },
102159
102170
  {
102160
102171
  "name": "--color-component-empty-state-medallion-bg-from",
102161
102172
  "value": "var(--color-white)"
@@ -103230,6 +103241,10 @@
103230
103241
  "name": "--color-component-app-shell-bg",
103231
103242
  "value": "var(--color-slate-975)"
103232
103243
  },
103244
+ {
103245
+ "name": "--color-component-app-shell-ambient-dot",
103246
+ "value": "--alpha(var(--color-slate-50) / 15%)"
103247
+ },
103233
103248
  {
103234
103249
  "name": "--color-component-empty-state-medallion-bg-from",
103235
103250
  "value": "--alpha(var(--color-white) / 7%)"
@@ -0,0 +1,336 @@
1
+ /**
2
+ * AppShell Frame ambient (WDS-181).
3
+ *
4
+ * Vocabulary: the **Frame** is the shell's gray surround — the header bar plus
5
+ * the rail, everything that is not the canvas. **Ambient** is the barely-there
6
+ * life painted into it.
7
+ *
8
+ * Three stacked layers, all decorative, all under the regions:
9
+ * 1. blooms — seven large soft gray spots: four in the rail (two lifts, two
10
+ * shades) and three flat ellipses crossing
11
+ * the top edge so the header carries the same light. They wander
12
+ * on 60–90 s loops, stretching as they move. This is the "mesh gradient" of the 21st.dev
13
+ * builder, in the shell's own tones.
14
+ * 2. grain — desaturated fractal noise tiled as a background image, so it
15
+ * rasterises once and never repaints with the blooms.
16
+ * 3. dots — a 12px halftone raster (the auth-page scanner's grid) pooled
17
+ * in the bottom-left of the rail, fading upward to nothing;
18
+ * patches of it surface and dissolve under two drifting noise
19
+ * masks, so the grid is never fully there.
20
+ *
21
+ * Softness is baked into the gradient stops rather than `filter: blur()`:
22
+ * a blur on a transform-animated layer is re-run by the GPU every frame, a
23
+ * gradient is rasterised once and then only composited.
24
+ *
25
+ * The canvas (`AppShellRemote`) sits on top, so none of this shows under page
26
+ * content. Colours are theme tokens in semantic.css (light and dark), derived
27
+ * from the Frame token; only grain opacity/blend live in this file.
28
+ */
29
+
30
+ /* Colours are theme tokens (`--color-component-app-shell-ambient-*` in
31
+ semantic.css, beside the Frame token they derive from). Only the non-colour
32
+ knobs live here. */
33
+ :root {
34
+ --app-shell-ambient-grain-opacity: 0.36;
35
+ --app-shell-ambient-grain-blend: multiply;
36
+ }
37
+
38
+ [data-theme='dark'] {
39
+ --app-shell-ambient-grain-opacity: 0.28;
40
+ --app-shell-ambient-grain-blend: screen;
41
+ }
42
+
43
+ @utility app-shell-ambient {
44
+ position: absolute;
45
+ inset: 0;
46
+ z-index: -1;
47
+ overflow: hidden;
48
+ pointer-events: none;
49
+ contain: layout paint;
50
+ }
51
+
52
+ /* One soft spot. Each `app-shell-ambient-bloom-N` only sets its colour (`--c`), shape,
53
+ size, position and drift path; the falloff below approximates a Gaussian so no
54
+ `filter: blur()` is needed. */
55
+ @utility app-shell-ambient-bloom {
56
+ --c: transparent;
57
+ --shape: circle;
58
+ position: absolute;
59
+ border-radius: 50%;
60
+ will-change: transform;
61
+ background: radial-gradient(
62
+ var(--shape),
63
+ var(--c) 0%,
64
+ color-mix(in srgb, var(--c) 78%, transparent) 22%,
65
+ color-mix(in srgb, var(--c) 40%, transparent) 46%,
66
+ color-mix(in srgb, var(--c) 12%, transparent) 64%,
67
+ transparent 78%
68
+ );
69
+
70
+ @media (prefers-reduced-motion: reduce) {
71
+ animation: none;
72
+ }
73
+ }
74
+
75
+ @utility app-shell-ambient-bloom-1 {
76
+ --c: var(--color-component-app-shell-ambient-lift);
77
+ width: 760px;
78
+ height: 760px;
79
+ left: -280px;
80
+ bottom: -240px;
81
+ animation: app-shell-ambient-drift-1 60s ease-in-out infinite;
82
+ }
83
+
84
+ @utility app-shell-ambient-bloom-2 {
85
+ --c: var(--color-component-app-shell-ambient-shade);
86
+ width: 680px;
87
+ height: 680px;
88
+ left: -140px;
89
+ top: 8%;
90
+ animation: app-shell-ambient-drift-2 74s ease-in-out infinite;
91
+ }
92
+
93
+ @utility app-shell-ambient-bloom-3 {
94
+ --c: var(--color-component-app-shell-ambient-lift-soft);
95
+ width: 540px;
96
+ height: 540px;
97
+ left: -170px;
98
+ top: 46%;
99
+ animation: app-shell-ambient-drift-3 66s ease-in-out infinite;
100
+ }
101
+
102
+ @utility app-shell-ambient-bloom-4 {
103
+ --c: var(--color-component-app-shell-ambient-shade-soft);
104
+ width: 620px;
105
+ height: 620px;
106
+ left: 30px;
107
+ bottom: -190px;
108
+ animation: app-shell-ambient-drift-4 82s ease-in-out infinite;
109
+ }
110
+
111
+ /* Header strip: three wide, flat ellipses crossing the top edge so the bar
112
+ catches an echo of the rail's light — a shade over the centre, a lift toward
113
+ the actions on the right, and a shade bridging the corner past the logo so
114
+ the bar and the rail read as one fluid, not two zones. */
115
+ @utility app-shell-ambient-bloom-5 {
116
+ --c: var(--color-component-app-shell-ambient-header);
117
+ --shape: ellipse;
118
+ width: clamp(1000px, 55vw, 1400px);
119
+ height: 540px;
120
+ left: 22%;
121
+ top: -360px;
122
+ animation: app-shell-ambient-drift-5 90s ease-in-out infinite;
123
+ }
124
+
125
+ @utility app-shell-ambient-bloom-6 {
126
+ --c: var(--color-component-app-shell-ambient-lift);
127
+ --shape: ellipse;
128
+ width: clamp(1100px, 60vw, 1500px);
129
+ height: 580px;
130
+ right: -300px;
131
+ top: -390px;
132
+ animation: app-shell-ambient-drift-6 78s ease-in-out infinite;
133
+ }
134
+
135
+ @utility app-shell-ambient-bloom-7 {
136
+ --c: var(--color-component-app-shell-ambient-header);
137
+ --shape: ellipse;
138
+ width: clamp(780px, 50vw, 1100px);
139
+ height: 500px;
140
+ left: 20px;
141
+ top: -350px;
142
+ animation: app-shell-ambient-drift-7 70s ease-in-out infinite;
143
+ }
144
+
145
+ /* Drift paths. Each spot wanders through four waypoints instead of a straight
146
+ back-and-forth, stretching along the direction it moves (a liquid smearing
147
+ on a surface) and breathing a little in opacity. Transform + opacity only,
148
+ so the rasterised gradient is never repainted, just composited. */
149
+ @keyframes app-shell-ambient-drift-1 {
150
+ 0% {
151
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
152
+ opacity: 1;
153
+ }
154
+ 33% {
155
+ transform: translate(70px, -90px) rotate(6deg) scale(1.16, 0.92);
156
+ opacity: 0.9;
157
+ }
158
+ 66% {
159
+ transform: translate(30px, -160px) rotate(-4deg) scale(0.94, 1.14);
160
+ opacity: 1;
161
+ }
162
+ 100% {
163
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
164
+ opacity: 1;
165
+ }
166
+ }
167
+ @keyframes app-shell-ambient-drift-2 {
168
+ 0% {
169
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
170
+ opacity: 0.95;
171
+ }
172
+ 33% {
173
+ transform: translate(-50px, 110px) rotate(-8deg) scale(0.9, 1.18);
174
+ opacity: 1;
175
+ }
176
+ 66% {
177
+ transform: translate(40px, 170px) rotate(5deg) scale(1.2, 0.9);
178
+ opacity: 0.88;
179
+ }
180
+ 100% {
181
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
182
+ opacity: 0.95;
183
+ }
184
+ }
185
+ @keyframes app-shell-ambient-drift-3 {
186
+ 0% {
187
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
188
+ opacity: 1;
189
+ }
190
+ 33% {
191
+ transform: translate(60px, -120px) rotate(10deg) scale(1.22, 0.88);
192
+ opacity: 0.85;
193
+ }
194
+ 66% {
195
+ transform: translate(-30px, -200px) rotate(-6deg) scale(0.92, 1.16);
196
+ opacity: 1;
197
+ }
198
+ 100% {
199
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
200
+ opacity: 1;
201
+ }
202
+ }
203
+ @keyframes app-shell-ambient-drift-4 {
204
+ 0% {
205
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
206
+ opacity: 0.9;
207
+ }
208
+ 33% {
209
+ transform: translate(-80px, -60px) rotate(-7deg) scale(1.18, 0.9);
210
+ opacity: 1;
211
+ }
212
+ 66% {
213
+ transform: translate(-40px, -140px) rotate(4deg) scale(0.9, 1.2);
214
+ opacity: 0.86;
215
+ }
216
+ 100% {
217
+ transform: translate(0, 0) rotate(0deg) scale(1, 1);
218
+ opacity: 0.9;
219
+ }
220
+ }
221
+ @keyframes app-shell-ambient-drift-5 {
222
+ 0% {
223
+ transform: translate(0, 0) scale(1, 1);
224
+ opacity: 1;
225
+ }
226
+ 33% {
227
+ transform: translate(160px, 40px) scale(1.14, 0.94);
228
+ opacity: 0.85;
229
+ }
230
+ 66% {
231
+ transform: translate(260px, 10px) scale(0.96, 1.1);
232
+ opacity: 1;
233
+ }
234
+ 100% {
235
+ transform: translate(0, 0) scale(1, 1);
236
+ opacity: 1;
237
+ }
238
+ }
239
+ @keyframes app-shell-ambient-drift-6 {
240
+ 0% {
241
+ transform: translate(0, 0) scale(1, 1);
242
+ opacity: 0.95;
243
+ }
244
+ 33% {
245
+ transform: translate(-200px, 30px) scale(1.12, 0.92);
246
+ opacity: 1;
247
+ }
248
+ 66% {
249
+ transform: translate(-320px, 60px) scale(0.94, 1.12);
250
+ opacity: 0.85;
251
+ }
252
+ 100% {
253
+ transform: translate(0, 0) scale(1, 1);
254
+ opacity: 0.95;
255
+ }
256
+ }
257
+ @keyframes app-shell-ambient-drift-7 {
258
+ 0% {
259
+ transform: translate(0, 0) scale(1, 1);
260
+ opacity: 1;
261
+ }
262
+ 33% {
263
+ transform: translate(120px, 50px) scale(1.16, 0.9);
264
+ opacity: 0.88;
265
+ }
266
+ 66% {
267
+ transform: translate(200px, 20px) scale(0.92, 1.12);
268
+ opacity: 1;
269
+ }
270
+ 100% {
271
+ transform: translate(0, 0) scale(1, 1);
272
+ opacity: 1;
273
+ }
274
+ }
275
+
276
+ @utility app-shell-ambient-grain {
277
+ position: absolute;
278
+ inset: 0;
279
+ opacity: var(--app-shell-ambient-grain-opacity);
280
+ mix-blend-mode: var(--app-shell-ambient-grain-blend);
281
+ background-size: 256px 256px;
282
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.24'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
283
+ }
284
+
285
+ /* Dot cluster. Confined to the bottom-left of the Frame (the only place it is
286
+ ever visible) so the animated masks repaint a small box, not the viewport.
287
+ Three masks intersect: the vertical fade, and two tiles of low-frequency
288
+ noise drifting in opposite directions. Dots show only where both noise
289
+ tiles are open, so patches of dots surface and dissolve in place over a
290
+ couple of minutes instead of sliding across. */
291
+ @utility app-shell-ambient-dots {
292
+ position: absolute;
293
+ left: 0;
294
+ bottom: 0;
295
+ width: min(100%, 480px);
296
+ height: min(560px, 64vh);
297
+ background-image: radial-gradient(circle at center, var(--color-component-app-shell-ambient-dot) 0.9px, transparent 1.1px);
298
+ background-size: 12px 12px;
299
+ background-position: 6px 6px;
300
+ mask-image:
301
+ linear-gradient(
302
+ to top,
303
+ #000 0,
304
+ rgb(0 0 0 / 0.75) 22%,
305
+ rgb(0 0 0 / 0.4) 48%,
306
+ rgb(0 0 0 / 0.12) 74%,
307
+ transparent 94%
308
+ ),
309
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.011' numOctaves='2' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 0 -6.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
310
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.014' numOctaves='2' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 0 -6.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
311
+ mask-size:
312
+ 100% 100%,
313
+ 400px 400px,
314
+ 400px 400px;
315
+ mask-repeat: no-repeat, repeat, repeat;
316
+ mask-composite: intersect;
317
+ mask-position:
318
+ 0 0,
319
+ 0 0,
320
+ 0 0;
321
+ animation: app-shell-ambient-dots-shift 150s linear infinite;
322
+
323
+ @media (prefers-reduced-motion: reduce) {
324
+ animation: none;
325
+ }
326
+ }
327
+
328
+ /* Whole-tile offsets (multiples of 400px) so the loop is seamless. */
329
+ @keyframes app-shell-ambient-dots-shift {
330
+ to {
331
+ mask-position:
332
+ 0 0,
333
+ 400px -400px,
334
+ -800px 400px;
335
+ }
336
+ }
@@ -28,6 +28,7 @@
28
28
  @import './components/progress.css';
29
29
  @import './components/product-nav.css';
30
30
  @import './components/animated-background.css';
31
+ @import './components/app-shell.css';
31
32
  @import './components/chart.css';
32
33
 
33
34
  [data-scope='scroll-area'][data-part='viewport'] {
@@ -216,6 +216,38 @@
216
216
  --color-component-toast-ic-info: var(--color-blue-400);
217
217
  --color-component-app-shell-bg: var(--color-slate-100);
218
218
 
219
+ /* AppShell Frame ambient — the soft spots and dot raster painted into the
220
+ * Frame (header + rail). Every tone derives from the Frame token above, so
221
+ * a branded Frame (any hue) lights and shades itself: "lift" mixes the base
222
+ * toward white, "shade" toward black, in oklch to keep the hue honest.
223
+ * Achromatic by design: no accent tint. */
224
+ --color-component-app-shell-ambient-lift: color-mix(
225
+ in oklch,
226
+ var(--color-component-app-shell-bg),
227
+ white 70%
228
+ );
229
+ --color-component-app-shell-ambient-lift-soft: color-mix(
230
+ in srgb,
231
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 45%) 70%,
232
+ transparent
233
+ );
234
+ --color-component-app-shell-ambient-shade: color-mix(
235
+ in srgb,
236
+ color-mix(in oklch, var(--color-component-app-shell-bg), black 10%) 70%,
237
+ transparent
238
+ );
239
+ --color-component-app-shell-ambient-shade-soft: color-mix(
240
+ in srgb,
241
+ color-mix(in oklch, var(--color-component-app-shell-bg), black 5%) 65%,
242
+ transparent
243
+ );
244
+ --color-component-app-shell-ambient-header: color-mix(
245
+ in srgb,
246
+ color-mix(in oklch, var(--color-component-app-shell-bg), black 7%) 22%,
247
+ transparent
248
+ );
249
+ --color-component-app-shell-ambient-dot: --alpha(var(--color-slate-500) / 28%);
250
+
219
251
  /* EmptyState medallion — the small raised tile behind the empty-state icon.
220
252
  * Reads as a lit surface: a top-down gradient, a hairline border, a soft
221
253
  * drop shadow and an inner top highlight. Kept as tokens (not utilities)
@@ -630,6 +662,35 @@
630
662
  --color-component-toast-ic-info: var(--color-blue-600);
631
663
  --color-component-app-shell-bg: var(--color-slate-975);
632
664
 
665
+ /* AppShell Frame ambient (dark) — on a near-black base only lifts read, so
666
+ * every spot mixes toward white. */
667
+ --color-component-app-shell-ambient-lift: color-mix(
668
+ in srgb,
669
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 11%) 95%,
670
+ transparent
671
+ );
672
+ --color-component-app-shell-ambient-lift-soft: color-mix(
673
+ in srgb,
674
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 14%) 70%,
675
+ transparent
676
+ );
677
+ --color-component-app-shell-ambient-shade: color-mix(
678
+ in srgb,
679
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 18%) 60%,
680
+ transparent
681
+ );
682
+ --color-component-app-shell-ambient-shade-soft: color-mix(
683
+ in srgb,
684
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 6%) 95%,
685
+ transparent
686
+ );
687
+ --color-component-app-shell-ambient-header: color-mix(
688
+ in srgb,
689
+ color-mix(in oklch, var(--color-component-app-shell-bg), white 10%) 30%,
690
+ transparent
691
+ );
692
+ --color-component-app-shell-ambient-dot: --alpha(var(--color-slate-50) / 15%);
693
+
633
694
  /* EmptyState medallion (dark) — same recipe inverted: the gradient and the
634
695
  * top highlight become low-alpha white, the shadows deepen to near-black. */
635
696
  --color-component-empty-state-medallion-bg-from: --alpha(var(--color-white) / 7%);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.22.3-rc-fix-date-utils-honour-format-context.1",
3
+ "version": "1.23.0",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",