@wallarm-org/design-system 1.24.0 → 1.26.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.
Files changed (41) hide show
  1. package/README.md +2 -0
  2. package/dist/components/AppShell/story-content/_storyAccountDropdown.d.ts +5 -2
  3. package/dist/components/AppShell/story-content/_storyAccountDropdown.js +110 -55
  4. package/dist/components/AppShell/story-content/_storyConfigRenderer.d.ts +4 -1
  5. package/dist/components/AppShell/story-content/_storyConfigRenderer.js +8 -5
  6. package/dist/components/AppShell/story-content/index.d.ts +1 -1
  7. package/dist/components/AppShell/story-content/index.js +1 -1
  8. package/dist/components/DropdownMenu/DropdownMenuRadioItem.js +1 -1
  9. package/dist/components/NavPanel/NavPanelBack.js +1 -1
  10. package/dist/components/NavPanel/NavPanelGroupItem.js +8 -1
  11. package/dist/components/NavPanel/NavPanelGroupLabel.js +1 -1
  12. package/dist/components/NavPanel/NavPanelSkeleton.js +1 -1
  13. package/dist/components/NavPanel/classes.js +6 -6
  14. package/dist/components/NavRail/NavRail.d.ts +7 -0
  15. package/dist/components/NavRail/NavRail.js +5 -3
  16. package/dist/components/NavRail/NavRailBody.js +6 -1
  17. package/dist/components/NavRail/NavRailContext.d.ts +2 -1
  18. package/dist/components/NavRail/NavRailContext.js +1 -1
  19. package/dist/components/NavRail/NavRailFooter.js +6 -1
  20. package/dist/components/NavRail/NavRailItem.d.ts +4 -0
  21. package/dist/components/NavRail/NavRailItem.js +33 -7
  22. package/dist/components/NavRail/NavRailSkeleton.js +24 -4
  23. package/dist/components/NavRail/classes.d.ts +13 -1
  24. package/dist/components/NavRail/classes.js +55 -9
  25. package/dist/components/NavRail/index.d.ts +1 -1
  26. package/dist/components/SearchModal/SearchModal.js +1 -0
  27. package/dist/components/Skeleton/Skeleton.d.ts +1 -1
  28. package/dist/components/Skeleton/Skeleton.js +1 -0
  29. package/dist/components/ThemeProvider/ThemeProvider.d.ts +4 -1
  30. package/dist/components/ThemeProvider/ThemeProvider.js +18 -2
  31. package/dist/components/ThemeProvider/ThemeProviderContext.d.ts +3 -1
  32. package/dist/components/ThemeProvider/ThemeProviderContext.js +3 -1
  33. package/dist/components/ThemeProvider/index.d.ts +1 -1
  34. package/dist/components/ThemeProvider/types.d.ts +2 -0
  35. package/dist/index.d.ts +2 -2
  36. package/dist/metadata/components.json +56 -9
  37. package/dist/theme/components/app-shell.css +11 -0
  38. package/dist/theme/index.css +4 -0
  39. package/dist/theme/radius.css +1 -0
  40. package/dist/theme/semantic.css +79 -0
  41. package/package.json +1 -1
@@ -2,20 +2,40 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
4
  import { Skeleton } from "../Skeleton/index.js";
5
+ import { navRailSkeletonVariants } from "./classes.js";
6
+ import { useNavRailContext } from "./NavRailContext.js";
7
+ const SKELETON_BARS = {
8
+ expanded: {
9
+ width: '100%',
10
+ height: '28px'
11
+ },
12
+ collapsed: {
13
+ width: '28px',
14
+ height: '28px'
15
+ },
16
+ compact: {
17
+ width: '54px',
18
+ height: '38px'
19
+ }
20
+ };
5
21
  const NavRailSkeleton = ({ ref, className, count = 4, ...props })=>{
22
+ const { mode } = useNavRailContext();
6
23
  const testId = useTestId('skeleton');
24
+ const bar = SKELETON_BARS[mode];
7
25
  return /*#__PURE__*/ jsx("div", {
8
26
  ...props,
9
27
  ref: ref,
10
28
  "data-slot": "nav-rail-skeleton",
11
29
  "data-testid": testId,
12
- className: cn('flex flex-col gap-6', className),
30
+ className: cn(navRailSkeletonVariants({
31
+ mode
32
+ }), className),
13
33
  children: Array.from({
14
34
  length: count
15
35
  }, (_, i)=>/*#__PURE__*/ jsx(Skeleton, {
16
- width: "100%",
17
- height: "28px",
18
- rounded: 6
36
+ width: bar.width,
37
+ height: bar.height,
38
+ rounded: 10
19
39
  }, i))
20
40
  });
21
41
  };
@@ -1,6 +1,18 @@
1
1
  export declare const navRailVariants: (props?: ({
2
- collapsed?: boolean | null | undefined;
2
+ mode?: "collapsed" | "compact" | "expanded" | null | undefined;
3
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
4
+ export declare const navRailBodyVariants: (props?: ({
5
+ mode?: "collapsed" | "compact" | "expanded" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ export declare const navRailFooterVariants: (props?: ({
8
+ mode?: "collapsed" | "compact" | "expanded" | null | undefined;
9
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
+ export declare const navRailSkeletonVariants: (props?: ({
11
+ mode?: "collapsed" | "compact" | "expanded" | null | undefined;
3
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
4
13
  export declare const navRailItemVariants: (props?: ({
14
+ mode?: "collapsed" | "compact" | "expanded" | null | undefined;
5
15
  active?: boolean | null | undefined;
16
+ avatar?: boolean | null | undefined;
6
17
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
18
+ export declare const navRailItemCompactLabelClassName = "line-clamp-2 w-56 shrink-0 text-center text-2xs font-medium text-ellipsis break-normal";
@@ -1,24 +1,70 @@
1
1
  import { cva } from "class-variance-authority";
2
2
  const navRailVariants = cva('flex h-full shrink-0 flex-col overflow-hidden px-8 pt-6 pb-12 transition-[width] duration-200 ease-in-out', {
3
3
  variants: {
4
- collapsed: {
5
- true: 'w-[48px]',
6
- false: 'w-[184px]'
4
+ mode: {
5
+ expanded: 'w-[184px]',
6
+ collapsed: 'w-[48px]',
7
+ compact: 'w-[78px]'
7
8
  }
8
9
  },
9
10
  defaultVariants: {
10
- collapsed: false
11
+ mode: 'expanded'
11
12
  }
12
13
  });
13
- const navRailItemVariants = cva('overlay flex h-32 w-full cursor-pointer items-center rounded-6 p-8 text-sm text-text-primary transition-colors outline-none data-[state=open]:overlay-states-primary-active data-[state=open]:opacity-100', {
14
+ const railGap = {
15
+ expanded: 'gap-2',
16
+ collapsed: 'gap-2',
17
+ compact: 'gap-4'
18
+ };
19
+ const navRailBodyVariants = cva('flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-none [scrollbar-width:thin]', {
14
20
  variants: {
21
+ mode: railGap
22
+ },
23
+ defaultVariants: {
24
+ mode: 'expanded'
25
+ }
26
+ });
27
+ const navRailFooterVariants = cva('mt-auto flex flex-col', {
28
+ variants: {
29
+ mode: railGap
30
+ },
31
+ defaultVariants: {
32
+ mode: 'expanded'
33
+ }
34
+ });
35
+ const navRailSkeletonVariants = cva('flex flex-col items-center py-2', {
36
+ variants: {
37
+ mode: {
38
+ expanded: 'gap-6',
39
+ collapsed: 'gap-6',
40
+ compact: 'gap-8'
41
+ }
42
+ },
43
+ defaultVariants: {
44
+ mode: 'expanded'
45
+ }
46
+ });
47
+ const navRailItemVariants = cva('overlay flex w-full cursor-pointer items-center rounded-10 text-sm text-text-primary transition-colors outline-none data-[state=open]:overlay-states-primary-active', {
48
+ variants: {
49
+ mode: {
50
+ expanded: 'h-32 p-8',
51
+ collapsed: 'h-32 p-8',
52
+ compact: 'min-h-42 flex-col justify-center gap-2 px-8 py-6'
53
+ },
15
54
  active: {
16
- true: 'overlay-states-primary-active',
17
- false: 'opacity-85 hover:opacity-100 hover:overlay-states-primary-hover focus-visible:opacity-100 focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
55
+ true: 'overlay-states-primary-active branded:overlay-states-brand-active branded:data-[state=open]:overlay-states-brand-active branded:text-text-brand',
56
+ false: 'hover:overlay-states-primary-hover focus-visible:overlay-states-primary-hover active:overlay-states-primary-pressed'
57
+ },
58
+ avatar: {
59
+ true: 'branded:hover:overlay-states-brand-hover branded:focus-visible:overlay-states-brand-hover branded:active:overlay-states-brand-pressed branded:data-[state=open]:overlay-states-brand-active',
60
+ false: ''
18
61
  }
19
62
  },
20
63
  defaultVariants: {
21
- active: false
64
+ mode: 'expanded',
65
+ active: false,
66
+ avatar: false
22
67
  }
23
68
  });
24
- export { navRailItemVariants, navRailVariants };
69
+ const navRailItemCompactLabelClassName = 'line-clamp-2 w-56 shrink-0 text-center text-2xs font-medium text-ellipsis break-normal';
70
+ export { navRailBodyVariants, navRailFooterVariants, navRailItemCompactLabelClassName, navRailItemVariants, navRailSkeletonVariants, navRailVariants };
@@ -1,6 +1,6 @@
1
1
  export { NavRail, type NavRailProps } from './NavRail';
2
2
  export { NavRailBody, type NavRailBodyProps } from './NavRailBody';
3
- export { useNavRailContext } from './NavRailContext';
3
+ export { type NavRailMode, useNavRailContext } from './NavRailContext';
4
4
  export { NavRailFooter, type NavRailFooterProps } from './NavRailFooter';
5
5
  export { NavRailItem, type NavRailItemProps } from './NavRailItem';
6
6
  export { NavRailSeparator, type NavRailSeparatorProps } from './NavRailSeparator';
@@ -29,6 +29,7 @@ const SearchModal = ({ children, open, onOpenChange, onQueryChange, 'data-testid
29
29
  children: /*#__PURE__*/ jsx("div", {
30
30
  "data-slot": "search-modal",
31
31
  "data-testid": testId,
32
+ className: "contents",
32
33
  children: /*#__PURE__*/ jsx(Dialog.Root, {
33
34
  open: open,
34
35
  onOpenChange: handleOpenChange,
@@ -3,7 +3,7 @@ import { type VariantProps } from 'class-variance-authority';
3
3
  import type { TestableProps } from '../../utils/testId';
4
4
  declare const skeletonVariants: (props?: ({
5
5
  transparent?: boolean | null | undefined;
6
- rounded?: "full" | "none" | 2 | 4 | 6 | 8 | 12 | 16 | 24 | 32 | null | undefined;
6
+ rounded?: "full" | "none" | 2 | 4 | 6 | 8 | 10 | 12 | 16 | 24 | 32 | null | undefined;
7
7
  withDimensions?: boolean | null | undefined;
8
8
  withChildren?: boolean | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -14,6 +14,7 @@ const skeletonVariants = cva(cn('overflow-hidden', 'animate-skeleton'), {
14
14
  4: 'rounded-4',
15
15
  6: 'rounded-6',
16
16
  8: 'rounded-8',
17
+ 10: 'rounded-10',
17
18
  12: 'rounded-12',
18
19
  16: 'rounded-16',
19
20
  24: 'rounded-24',
@@ -1,9 +1,12 @@
1
1
  import { type FC, type ReactNode } from 'react';
2
- import type { Theme } from './types';
2
+ import type { FrameStyle, Theme } from './types';
3
3
  interface ThemeProviderProps {
4
4
  children: ReactNode;
5
5
  defaultTheme?: Theme;
6
6
  storageKey?: string;
7
+ /** Frame style used until the user picks one. */
8
+ defaultFrameStyle?: FrameStyle;
9
+ frameStyleStorageKey?: string;
7
10
  }
8
11
  export declare const ThemeProvider: FC<ThemeProviderProps>;
9
12
  export {};
@@ -1,23 +1,39 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import { ThemeProviderContext } from "./ThemeProviderContext.js";
4
- const ThemeProvider = ({ children, defaultTheme = 'light', storageKey = 'wasd-theme', ...props })=>{
4
+ const ThemeProvider = ({ children, defaultTheme = 'light', storageKey = 'wasd-theme', defaultFrameStyle = 'neutral', frameStyleStorageKey = 'wasd-frame-style', ...props })=>{
5
5
  const [theme, setTheme] = useState(()=>localStorage.getItem(storageKey) || defaultTheme);
6
+ const [frameStyle, setFrameStyle] = useState(()=>{
7
+ const stored = localStorage.getItem(frameStyleStorageKey);
8
+ return 'neutral' === stored || 'branded' === stored ? stored : defaultFrameStyle;
9
+ });
6
10
  useEffect(()=>{
7
11
  const root = window.document.documentElement;
8
12
  root.setAttribute('data-theme', theme);
9
13
  }, [
10
14
  theme
11
15
  ]);
16
+ useEffect(()=>{
17
+ window.document.documentElement.setAttribute('data-frame-style', frameStyle);
18
+ }, [
19
+ frameStyle
20
+ ]);
12
21
  const value = useMemo(()=>({
13
22
  theme,
14
23
  setTheme: (theme)=>{
15
24
  localStorage.setItem(storageKey, theme);
16
25
  setTheme(theme);
26
+ },
27
+ frameStyle,
28
+ setFrameStyle: (frameStyle)=>{
29
+ localStorage.setItem(frameStyleStorageKey, frameStyle);
30
+ setFrameStyle(frameStyle);
17
31
  }
18
32
  }), [
19
33
  theme,
20
- storageKey
34
+ storageKey,
35
+ frameStyle,
36
+ frameStyleStorageKey
21
37
  ]);
22
38
  return /*#__PURE__*/ jsx(ThemeProviderContext.Provider, {
23
39
  ...props,
@@ -1,6 +1,8 @@
1
- import type { Theme } from './types';
1
+ import type { FrameStyle, Theme } from './types';
2
2
  export interface ThemeProviderState {
3
3
  theme: Theme;
4
4
  setTheme: (theme: Theme) => void;
5
+ frameStyle: FrameStyle;
6
+ setFrameStyle: (frameStyle: FrameStyle) => void;
5
7
  }
6
8
  export declare const ThemeProviderContext: import("react").Context<ThemeProviderState>;
@@ -1,6 +1,8 @@
1
1
  import { createContext } from "react";
2
2
  const ThemeProviderContext = createContext({
3
3
  theme: 'light',
4
- setTheme: ()=>null
4
+ setTheme: ()=>null,
5
+ frameStyle: 'neutral',
6
+ setFrameStyle: ()=>null
5
7
  });
6
8
  export { ThemeProviderContext };
@@ -1,3 +1,3 @@
1
1
  export { ThemeProvider } from './ThemeProvider';
2
- export type { Theme } from './types';
2
+ export type { FrameStyle, Theme } from './types';
3
3
  export { useTheme } from './useTheme';
@@ -1 +1,3 @@
1
1
  export type Theme = 'dark' | 'light';
2
+ /** The Frame's look inside a theme: the neutral gray surround, or the warm branded one. */
3
+ export type FrameStyle = 'neutral' | 'branded';
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ export { Link, type LinkProps } from './components/Link';
52
52
  export { List, ListIcon, type ListIconProps, ListItem, type ListItemProps, type ListProps, } from './components/List';
53
53
  export { Loader, type LoaderProps } from './components/Loader';
54
54
  export { Logo, type LogoProps, type LogoSize, type LogoStyle, type LogoType, } from './components/Logo';
55
- export { NavRail, NavRailBody, type NavRailBodyProps, NavRailFooter, type NavRailFooterProps, NavRailItem, type NavRailItemProps, type NavRailProps, NavRailSeparator, type NavRailSeparatorProps, NavRailSkeleton, type NavRailSkeletonProps, } from './components/NavRail';
55
+ export { NavRail, NavRailBody, type NavRailBodyProps, NavRailFooter, type NavRailFooterProps, NavRailItem, type NavRailItemProps, type NavRailMode, type NavRailProps, NavRailSeparator, type NavRailSeparatorProps, NavRailSkeleton, type NavRailSkeletonProps, } from './components/NavRail';
56
56
  export { NumberInput, type NumberInputProps } from './components/NumberInput';
57
57
  export { NumericBadge, type NumericBadgeProps, } from './components/NumericBadge';
58
58
  export { OverflowTooltip, OverflowTooltipContent, type OverflowTooltipContentProps, type OverflowTooltipProps, } from './components/OverflowTooltip';
@@ -87,7 +87,7 @@ export { Tabs, TabsButton, TabsContent, TabsLineActions, TabsList, TabsSeparator
87
87
  export { Tag, TagClose, type TagProps } from './components/Tag';
88
88
  export { Text, type TextProps } from './components/Text';
89
89
  export { Textarea, type TextareaProps } from './components/Textarea';
90
- export { type Theme, ThemeProvider, useTheme, } from './components/ThemeProvider';
90
+ export { type FrameStyle, type Theme, ThemeProvider, useTheme, } from './components/ThemeProvider';
91
91
  export { TimeInput, type TimeInputProps } from './components/TimeInput';
92
92
  export { Timeline, TimelineConnector, type TimelineConnectorProps, TimelineContent, type TimelineContentProps, TimelineDescription, type TimelineDescriptionProps, TimelineIndicator, type TimelineIndicatorProps, TimelineItem, type TimelineItemProps, type TimelineProps, TimelineSeparator, type TimelineSeparatorProps, TimelineTitle, type TimelineTitleProps, } from './components/Timeline';
93
93
  export { Toast, ToastActions, Toaster, toaster } from './components/Toast';
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.23.2",
3
- "generatedAt": "2026-09-27T21:57:31.353Z",
2
+ "version": "1.25.0",
3
+ "generatedAt": "2026-09-28T09:09:05.490Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -5341,17 +5341,22 @@
5341
5341
  "examples": [
5342
5342
  {
5343
5343
  "name": "Basic",
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}",
5345
- "description": "The three regions composed once — `AppShellHeader`, `AppShellRail`, `AppShellRemote` — with the content surface being the only part that scrolls."
5344
+ "code": "({ ambient }) => <Shell ambient={ambient} />",
5345
+ "description": "The three regions composed once — `AppShellHeader`, `AppShellRail`, `AppShellRemote` — with the content surface being the only part that scrolls. Pick a product in the rail to drill into it, and the account item at the bottom for appearance and sidebar mode."
5346
+ },
5347
+ {
5348
+ "name": "Loading",
5349
+ "code": "({ ambient, loading }) => (\n <Shell ambient={ambient} loading={loading} simulateProductLoading />\n)",
5350
+ "description": "How the shell looks while the platform loads. The header actions and the product rail hold skeletons that sit exactly where the real items land, so nothing jumps when they arrive. Turn `loading` off in Controls to watch the swap. Opening a product also shows its own panel and content loading."
5346
5351
  },
5347
5352
  {
5348
5353
  "name": "RevealFlow",
5349
- "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashDone, setSplashDone] = useState(false);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const [revealKey, setRevealKey] = useState(0);\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n useEffect(() => {\n if (splashDone) return;\n const timer = setTimeout(() => setSplashDone(true), 2000);\n return () => clearTimeout(timer);\n }, [splashDone]);\n\n const handleReplay = () => {\n setSplashDone(false);\n setRevealKey(k => k + 1);\n };\n\n if (!splashDone) {\n return (\n <div key={revealKey} className='h-screen w-screen bg-bg-page-bg'>\n <SplashScreen />\n </div>\n );\n }\n\n return (\n <AppShell key={revealKey} reveal>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\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 <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={handleReplay}>\n Replay animation\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}",
5354
+ "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashDone, setSplashDone] = useState(false);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const [revealKey, setRevealKey] = useState(0);\n const { theme, setTheme } = useTheme();\n const railMode = railModeFor(sidebarMode, activeProduct === 'home');\n\n useEffect(() => {\n if (splashDone) return;\n const timer = setTimeout(() => setSplashDone(true), 2000);\n return () => clearTimeout(timer);\n }, [splashDone]);\n\n const handleReplay = () => {\n setSplashDone(false);\n setRevealKey(k => k + 1);\n };\n\n if (!splashDone) {\n return (\n <div key={revealKey} className='h-screen w-screen bg-bg-page-bg'>\n <SplashScreen />\n </div>\n );\n }\n\n return (\n <AppShell key={revealKey} reveal>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail mode={railMode}>\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 <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={handleReplay}>\n Replay animation\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}",
5350
5355
  "description": "`reveal` animates the chrome in on the first application load only. Leave it unset for an ordinary screen; this exists for prototyping the boot moment itself."
5351
5356
  },
5352
5357
  {
5353
5358
  "name": "LoginFlow",
5354
- "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashVisible, setSplashVisible] = useState(true);\n const [showShell, setShowShell] = useState(false);\n const [revealed, setRevealed] = useState(false);\n const [flowKey, setFlowKey] = useState(0);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n useEffect(() => {\n if (!splashVisible) return;\n const timer = setTimeout(() => setSplashVisible(false), 2000);\n return () => clearTimeout(timer);\n }, [splashVisible]);\n\n const handleSignIn = () => {\n setShowShell(true);\n };\n\n const handleReplay = () => {\n setShowShell(false);\n setRevealed(false);\n setSplashVisible(true);\n setFlowKey(k => k + 1);\n };\n\n return (\n <div\n key={flowKey}\n className='relative h-screen w-screen overflow-hidden bg-component-app-shell-bg'\n >\n {!revealed && (\n <AnimatedBackground\n className='absolute inset-0'\n style={{\n opacity: showShell ? 0 : 1,\n transition: showShell ? 'opacity 400ms cubic-bezier(0.4, 0, 0.2, 1)' : undefined,\n }}\n />\n )}\n\n {!showShell && (\n <div className='absolute inset-0 flex items-center justify-center z-10'>\n <SplashScreen\n visible={splashVisible}\n shrinkTarget={CARD_DIMENSIONS}\n className='bg-bg-page-bg shadow-lg'\n >\n <div className='flex h-full w-full flex-col items-center justify-center gap-16 p-24'>\n <Text size='xl'>Sign In</Text>\n <div className='flex w-full flex-col gap-12'>\n <Input placeholder='Email' />\n <Input placeholder='Password' type='password' />\n </div>\n <Button variant='primary' color='brand' className='w-full' onClick={handleSignIn}>\n Sign In\n </Button>\n </div>\n </SplashScreen>\n </div>\n )}\n\n {showShell && (\n <div className='absolute inset-0'>\n <AppShell expandFrom={CARD_DIMENSIONS} onRevealed={() => setRevealed(true)}>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n\n <NavRailSeparator />\n\n <ProductNavItems activeProduct={activeProduct} />\n </NavRailBody>\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={handleReplay}>\n Replay animation\n </Button>\n </div>\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n </div>\n )}\n </div>\n );\n}",
5359
+ "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [splashVisible, setSplashVisible] = useState(true);\n const [showShell, setShowShell] = useState(false);\n const [revealed, setRevealed] = useState(false);\n const [flowKey, setFlowKey] = useState(0);\n const [sidebarMode, setSidebarMode] = useState<SidebarMode>('adaptive');\n const { theme, setTheme } = useTheme();\n const railMode = railModeFor(sidebarMode, activeProduct === 'home');\n\n useEffect(() => {\n if (!splashVisible) return;\n const timer = setTimeout(() => setSplashVisible(false), 2000);\n return () => clearTimeout(timer);\n }, [splashVisible]);\n\n const handleSignIn = () => {\n setShowShell(true);\n };\n\n const handleReplay = () => {\n setShowShell(false);\n setRevealed(false);\n setSplashVisible(true);\n setFlowKey(k => k + 1);\n };\n\n return (\n <div\n key={flowKey}\n className='relative h-screen w-screen overflow-hidden bg-component-app-shell-bg'\n >\n {!revealed && (\n <AnimatedBackground\n className='absolute inset-0'\n style={{\n opacity: showShell ? 0 : 1,\n transition: showShell ? 'opacity 400ms cubic-bezier(0.4, 0, 0.2, 1)' : undefined,\n }}\n />\n )}\n\n {!showShell && (\n <div className='absolute inset-0 flex items-center justify-center z-10'>\n <SplashScreen\n visible={splashVisible}\n shrinkTarget={CARD_DIMENSIONS}\n className='bg-bg-page-bg shadow-lg'\n >\n <div className='flex h-full w-full flex-col items-center justify-center gap-16 p-24'>\n <Text size='xl'>Sign In</Text>\n <div className='flex w-full flex-col gap-12'>\n <Input placeholder='Email' />\n <Input placeholder='Password' type='password' />\n </div>\n <Button variant='primary' color='brand' className='w-full' onClick={handleSignIn}>\n Sign In\n </Button>\n </div>\n </SplashScreen>\n </div>\n )}\n\n {showShell && (\n <div className='absolute inset-0'>\n <AppShell expandFrom={CARD_DIMENSIONS} onRevealed={() => setRevealed(true)}>\n <AppShellHeader>\n <TopHeader>\n <TopHeaderLogo href='/'>\n <WallarmLogo />\n </TopHeaderLogo>\n\n <TopHeaderActions>\n <HeaderActions />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail mode={railMode}>\n <NavRailBody>\n <NavRailItem\n icon={Home}\n label='Home'\n active={activeProduct === 'home'}\n onClick={() => navigateToProduct('home')}\n />\n\n <NavRailSeparator />\n\n <ProductNavItems activeProduct={activeProduct} />\n </NavRailBody>\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={handleReplay}>\n Replay animation\n </Button>\n </div>\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n </div>\n )}\n </div>\n );\n}",
5355
5360
  "description": "The whole entrance: the splash hands off, the content surface expands from the sign-in card into the full shell. `onRevealed` fires when it has settled."
5356
5361
  }
5357
5362
  ]
@@ -44743,10 +44748,17 @@
44743
44748
  "name": "NavRail",
44744
44749
  "importPath": "@wallarm-org/design-system/NavRail",
44745
44750
  "props": [
44751
+ {
44752
+ "name": "mode",
44753
+ "type": "NavRailMode | undefined",
44754
+ "required": false,
44755
+ "description": "How the rail lays out its items: `expanded` (icon beside label), `collapsed` (icon only) or\n`compact` (icon over a short label). Wins over `collapsed` when both are set."
44756
+ },
44746
44757
  {
44747
44758
  "name": "collapsed",
44748
44759
  "type": "boolean | undefined",
44749
44760
  "required": false,
44761
+ "description": "Shorthand for `mode='collapsed'`, kept so existing hosts keep working. Prefer `mode`.",
44750
44762
  "defaultValue": "false"
44751
44763
  },
44752
44764
  {
@@ -45022,7 +45034,16 @@
45022
45034
  ],
45023
45035
  "variants": [
45024
45036
  {
45025
- "name": "collapsed",
45037
+ "name": "mode",
45038
+ "options": [
45039
+ "expanded",
45040
+ "collapsed",
45041
+ "compact"
45042
+ ],
45043
+ "defaultValue": "expanded"
45044
+ },
45045
+ {
45046
+ "name": "active",
45026
45047
  "options": [
45027
45048
  "true",
45028
45049
  "false"
@@ -45030,7 +45051,7 @@
45030
45051
  "defaultValue": "false"
45031
45052
  },
45032
45053
  {
45033
- "name": "active",
45054
+ "name": "avatar",
45034
45055
  "options": [
45035
45056
  "true",
45036
45057
  "false"
@@ -45619,6 +45640,19 @@
45619
45640
  "required": false,
45620
45641
  "defaultValue": "false"
45621
45642
  },
45643
+ {
45644
+ "name": "avatar",
45645
+ "type": "boolean | undefined",
45646
+ "required": false,
45647
+ "description": "Seats the icon on a soft plate, for the signed-in user's item at the foot of the rail.",
45648
+ "defaultValue": "false"
45649
+ },
45650
+ {
45651
+ "name": "avatarSrc",
45652
+ "type": "string | undefined",
45653
+ "required": false,
45654
+ "description": "User photo for the plate (implies `avatar`). The icon stays underneath and returns if the photo fails to load."
45655
+ },
45622
45656
  {
45623
45657
  "name": "download",
45624
45658
  "type": "any",
@@ -78930,7 +78964,7 @@
78930
78964
  },
78931
78965
  {
78932
78966
  "name": "rounded",
78933
- "type": "{ none: string; full: string; 2: string; 4: string; 6: string; 8: string; 12: string; 16: string; 24: string; 32: string; }",
78967
+ "type": "{ none: string; full: string; 2: string; 4: string; 6: string; 8: string; 10: string; 12: string; 16: string; 24: string; 32: string; }",
78934
78968
  "required": false,
78935
78969
  "defaultValue": "6"
78936
78970
  },
@@ -78979,6 +79013,7 @@
78979
79013
  "4",
78980
79014
  "6",
78981
79015
  "8",
79016
+ "10",
78982
79017
  "12",
78983
79018
  "16",
78984
79019
  "24",
@@ -101447,6 +101482,10 @@
101447
101482
  "name": "--radius-8",
101448
101483
  "value": "8px"
101449
101484
  },
101485
+ {
101486
+ "name": "--radius-10",
101487
+ "value": "10px"
101488
+ },
101450
101489
  {
101451
101490
  "name": "--radius-12",
101452
101491
  "value": "12px"
@@ -102173,6 +102212,10 @@
102173
102212
  "name": "--color-component-app-shell-bg",
102174
102213
  "value": "var(--color-slate-100)"
102175
102214
  },
102215
+ {
102216
+ "name": "--color-component-app-shell-bg-branded",
102217
+ "value": "var(--color-w-orange-50)"
102218
+ },
102176
102219
  {
102177
102220
  "name": "--color-component-app-shell-ambient-dot",
102178
102221
  "value": "--alpha(var(--color-slate-500) / 28%)"
@@ -103251,6 +103294,10 @@
103251
103294
  "name": "--color-component-app-shell-bg",
103252
103295
  "value": "var(--color-slate-975)"
103253
103296
  },
103297
+ {
103298
+ "name": "--color-component-app-shell-bg-branded",
103299
+ "value": "#160e1a"
103300
+ },
103254
103301
  {
103255
103302
  "name": "--color-component-app-shell-ambient-dot",
103256
103303
  "value": "--alpha(var(--color-slate-50) / 15%)"
@@ -40,6 +40,17 @@
40
40
  --app-shell-ambient-grain-blend: screen;
41
41
  }
42
42
 
43
+ /* Branded Frame (WDS-182): multiplying or screening gray noise over the warm base dulls it
44
+ toward gray. Soft-light adds the same texture and keeps the orange. */
45
+ :root[data-frame-style='branded'] {
46
+ --app-shell-ambient-grain-opacity: 0.6;
47
+ --app-shell-ambient-grain-blend: soft-light;
48
+ }
49
+
50
+ :root[data-theme='dark'][data-frame-style='branded'] {
51
+ --app-shell-ambient-grain-opacity: 0.35;
52
+ }
53
+
43
54
  @utility app-shell-ambient {
44
55
  position: absolute;
45
56
  inset: 0;
@@ -31,6 +31,10 @@
31
31
  @import './components/app-shell.css';
32
32
  @import './components/chart.css';
33
33
 
34
+ /* Frame style (WDS-182): styles that apply only while the user has picked the Branded Frame.
35
+ ThemeProvider writes `data-frame-style` on <html>, so this reaches every micro-frontend. */
36
+ @custom-variant branded (&:where([data-frame-style='branded'] *));
37
+
34
38
  [data-scope='scroll-area'][data-part='viewport'] {
35
39
  scrollbar-width: none;
36
40
  &::-webkit-scrollbar {
@@ -3,6 +3,7 @@
3
3
  --radius-4: 4px;
4
4
  --radius-6: 6px;
5
5
  --radius-8: 8px;
6
+ --radius-10: 10px;
6
7
  --radius-12: 12px;
7
8
  --radius-16: 16px;
8
9
  --radius-24: 24px;
@@ -215,6 +215,11 @@
215
215
  --color-component-toast-ic-success: var(--color-green-400);
216
216
  --color-component-toast-ic-info: var(--color-blue-400);
217
217
  --color-component-app-shell-bg: var(--color-slate-100);
218
+ /* Branded Frame (WDS-182): the Frame fill when the user picks the Branded Frame style.
219
+ * Swapped in for the token above under `[data-frame-style='branded']` (end of file), so
220
+ * AppShell follows with no component change; the Ambient tones below get their own
221
+ * Branded recipe at the end of the file. */
222
+ --color-component-app-shell-bg-branded: var(--color-w-orange-50);
218
223
 
219
224
  /* AppShell Frame ambient — the soft spots and dot raster painted into the
220
225
  * Frame (header + rail). Every tone derives from the Frame token above, so
@@ -661,6 +666,7 @@
661
666
  --color-component-toast-ic-success: var(--color-green-600);
662
667
  --color-component-toast-ic-info: var(--color-blue-600);
663
668
  --color-component-app-shell-bg: var(--color-slate-975);
669
+ --color-component-app-shell-bg-branded: #160e1a;
664
670
 
665
671
  /* AppShell Frame ambient (dark) — on a near-black base only lifts read, so
666
672
  * every spot mixes toward white. */
@@ -876,3 +882,76 @@
876
882
  --color-data-viz-1: #ffffff;
877
883
  --temp-token: #ffffff;
878
884
  }
885
+
886
+ /* ========================================
887
+ * FRAME STYLE: BRANDED (WDS-182)
888
+ * ThemeProvider writes `data-frame-style` on <html>. Branded is not a value swap like
889
+ * dark: components switch to the existing brand tokens through the `branded:` variant
890
+ * (index.css). Only the Frame base and the Ambient tones change here. These rules are
891
+ * unlayered, so they beat the light theme values in `@layer theme`; the doubled
892
+ * specificity of `:root[...]` beats `[data-theme='dark']`.
893
+ *
894
+ * The neutral Ambient recipe mixes the base toward white/black in oklch. The achromatic
895
+ * end has no hue, so on a pale orange base the tones lose theirs and read gray. Branded
896
+ * tints the spots toward the brand oranges in oklab instead, which keeps the hue, and
897
+ * lifts only part-way to white so they stay warm. The grain's blend lives in app-shell.css.
898
+ * ======================================== */
899
+ :root[data-frame-style='branded'] {
900
+ --color-component-app-shell-bg: var(--color-component-app-shell-bg-branded);
901
+ --color-component-app-shell-ambient-lift: color-mix(
902
+ in oklab,
903
+ var(--color-component-app-shell-bg),
904
+ white 40%
905
+ );
906
+ --color-component-app-shell-ambient-lift-soft: color-mix(
907
+ in srgb,
908
+ color-mix(in oklab, var(--color-component-app-shell-bg), white 25%) 70%,
909
+ transparent
910
+ );
911
+ --color-component-app-shell-ambient-shade: color-mix(
912
+ in srgb,
913
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-300) 30%) 70%,
914
+ transparent
915
+ );
916
+ --color-component-app-shell-ambient-shade-soft: color-mix(
917
+ in srgb,
918
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-200) 30%) 65%,
919
+ transparent
920
+ );
921
+ --color-component-app-shell-ambient-header: color-mix(
922
+ in srgb,
923
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-300) 25%) 22%,
924
+ transparent
925
+ );
926
+ --color-component-app-shell-ambient-dot: --alpha(var(--color-w-orange-500) / 28%);
927
+ }
928
+
929
+ /* Dark: only a faint warm glow toward the deepest brand oranges, as quiet as the neutral lifts. */
930
+ :root[data-theme='dark'][data-frame-style='branded'] {
931
+ --color-component-app-shell-ambient-lift: color-mix(
932
+ in srgb,
933
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-900) 10%) 95%,
934
+ transparent
935
+ );
936
+ --color-component-app-shell-ambient-lift-soft: color-mix(
937
+ in srgb,
938
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-900) 14%) 70%,
939
+ transparent
940
+ );
941
+ --color-component-app-shell-ambient-shade: color-mix(
942
+ in srgb,
943
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-900) 12%) 60%,
944
+ transparent
945
+ );
946
+ --color-component-app-shell-ambient-shade-soft: color-mix(
947
+ in srgb,
948
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-950) 18%) 95%,
949
+ transparent
950
+ );
951
+ --color-component-app-shell-ambient-header: color-mix(
952
+ in srgb,
953
+ color-mix(in oklab, var(--color-component-app-shell-bg), var(--color-w-orange-900) 10%) 30%,
954
+ transparent
955
+ );
956
+ --color-component-app-shell-ambient-dot: --alpha(var(--color-w-orange-300) / 15%);
957
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",