@wallarm-org/design-system 0.54.0-rc-feature-shell.1 → 0.54.0-rc-feature-shell.2

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.
@@ -1,7 +1,10 @@
1
- import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
1
+ import { type FC, type HTMLAttributes, type ReactNode, type Ref } from 'react';
2
2
  import { type TestableProps } from '../../utils/testId';
3
3
  export interface AppShellProps extends HTMLAttributes<HTMLDivElement>, TestableProps {
4
4
  ref?: Ref<HTMLDivElement>;
5
5
  children?: ReactNode;
6
+ reveal?: boolean;
7
+ onRevealed?: () => void;
8
+ appeared?: boolean;
6
9
  }
7
10
  export declare const AppShell: FC<AppShellProps>;
@@ -1,16 +1,81 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { composeRefs } from "@radix-ui/react-compose-refs";
2
4
  import { cn } from "../../utils/cn.js";
3
5
  import { TestIdProvider } from "../../utils/testId.js";
4
- const AppShell = ({ ref, className, children, 'data-testid': testId, ...props })=>/*#__PURE__*/ jsx(TestIdProvider, {
6
+ import { AppShellContext } from "./AppShellContext.js";
7
+ const AppShell = ({ ref, className, children, reveal, onRevealed, appeared: appearedProp, 'data-testid': testId, ...props })=>{
8
+ const internalRef = useRef(null);
9
+ const onRevealedRef = useRef(onRevealed);
10
+ onRevealedRef.current = onRevealed;
11
+ const [phase, setPhase] = useState(reveal ? 'initial' : 'done');
12
+ const [targetRect, setTargetRect] = useState(null);
13
+ useEffect(()=>{
14
+ if ('initial' !== phase) return;
15
+ const shell = internalRef.current;
16
+ if (!shell) return;
17
+ let cancelled = false;
18
+ requestAnimationFrame(()=>{
19
+ requestAnimationFrame(()=>{
20
+ if (cancelled) return;
21
+ const remote = shell.querySelector('[data-slot="app-shell-remote"]');
22
+ if (!remote) {
23
+ setPhase('done');
24
+ onRevealedRef.current?.();
25
+ return;
26
+ }
27
+ const shellRect = shell.getBoundingClientRect();
28
+ const remoteRect = remote.getBoundingClientRect();
29
+ setTargetRect({
30
+ top: remoteRect.top - shellRect.top,
31
+ left: remoteRect.left - shellRect.left
32
+ });
33
+ setPhase('revealing');
34
+ });
35
+ });
36
+ return ()=>{
37
+ cancelled = true;
38
+ };
39
+ }, [
40
+ phase
41
+ ]);
42
+ const handleTransitionEnd = useCallback((e)=>{
43
+ if ('top' === e.propertyName) {
44
+ setPhase('done');
45
+ onRevealedRef.current?.();
46
+ }
47
+ }, []);
48
+ const appeared = appearedProp ?? 'done' === phase;
49
+ const shellContext = useMemo(()=>({
50
+ appeared
51
+ }), [
52
+ appeared
53
+ ]);
54
+ return /*#__PURE__*/ jsx(TestIdProvider, {
5
55
  value: testId,
6
- children: /*#__PURE__*/ jsx("div", {
7
- ...props,
8
- ref: ref,
9
- "data-slot": "app-shell",
10
- "data-testid": testId,
11
- className: cn('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),
12
- children: children
56
+ children: /*#__PURE__*/ jsx(AppShellContext.Provider, {
57
+ value: shellContext,
58
+ children: /*#__PURE__*/ jsxs("div", {
59
+ ...props,
60
+ ref: composeRefs(internalRef, ref),
61
+ "data-slot": "app-shell",
62
+ "data-testid": testId,
63
+ 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),
64
+ children: [
65
+ children,
66
+ 'done' !== phase && /*#__PURE__*/ jsx("div", {
67
+ className: cn('bg-bg-page-bg pointer-events-none z-50 absolute right-0 bottom-0', 'revealing' === phase && 'rounded-tl-12'),
68
+ style: {
69
+ top: 'revealing' === phase && targetRect ? targetRect.top : 0,
70
+ left: 'revealing' === phase && targetRect ? targetRect.left : 0,
71
+ transition: 'revealing' === phase ? 'top 500ms ease-in-out, left 500ms ease-in-out, border-radius 500ms ease-in-out' : void 0
72
+ },
73
+ onTransitionEnd: handleTransitionEnd
74
+ })
75
+ ]
76
+ })
13
77
  })
14
78
  });
79
+ };
15
80
  AppShell.displayName = 'AppShell';
16
81
  export { AppShell };
@@ -0,0 +1,6 @@
1
+ interface AppShellContextValue {
2
+ appeared: boolean;
3
+ }
4
+ export declare const AppShellContext: import("react").Context<AppShellContextValue>;
5
+ export declare const useAppShellAppeared: () => boolean;
6
+ export {};
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from "react";
2
+ const AppShellContext = createContext({
3
+ appeared: true
4
+ });
5
+ const useAppShellAppeared = ()=>useContext(AppShellContext).appeared;
6
+ export { AppShellContext, useAppShellAppeared };
@@ -1,14 +1,16 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
+ import { useAppShellAppeared } from "./AppShellContext.js";
4
5
  const AppShellHeader = ({ ref, className, children, ...props })=>{
5
6
  const testId = useTestId('header');
7
+ const appeared = useAppShellAppeared();
6
8
  return /*#__PURE__*/ jsx("header", {
7
9
  ...props,
8
10
  ref: ref,
9
11
  "data-slot": "app-shell-header",
10
12
  "data-testid": testId,
11
- className: cn('[grid-area:header]', className),
13
+ className: cn('[grid-area:header] transition-opacity duration-200 ease-in-out', !appeared && 'opacity-0', className),
12
14
  children: children
13
15
  });
14
16
  };
@@ -1,14 +1,16 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
+ import { useAppShellAppeared } from "./AppShellContext.js";
4
5
  const AppShellRail = ({ ref, className, children, ...props })=>{
5
6
  const testId = useTestId('rail');
7
+ const appeared = useAppShellAppeared();
6
8
  return /*#__PURE__*/ jsx("div", {
7
9
  ...props,
8
10
  ref: ref,
9
11
  "data-slot": "app-shell-rail",
10
12
  "data-testid": testId,
11
- className: cn('[grid-area:rail]', className),
13
+ className: cn('[grid-area:rail] transition-opacity duration-200 ease-in-out', !appeared && 'opacity-0', className),
12
14
  children: children
13
15
  });
14
16
  };
@@ -4,6 +4,5 @@ export interface NavRailProps extends HTMLAttributes<HTMLElement>, TestableProps
4
4
  ref?: Ref<HTMLElement>;
5
5
  children?: ReactNode;
6
6
  collapsed?: boolean;
7
- appeared?: boolean;
8
7
  }
9
8
  export declare const NavRail: FC<NavRailProps>;
@@ -6,7 +6,7 @@ import { cn } from "../../utils/cn.js";
6
6
  import { TestIdProvider } from "../../utils/testId.js";
7
7
  import { navRailVariants } from "./classes.js";
8
8
  import { NavRailContextProvider } from "./NavRailContext.js";
9
- const NavRail = ({ ref, collapsed = false, appeared, className, children, 'data-testid': testId, ...props })=>{
9
+ const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': testId, ...props })=>{
10
10
  const internalRef = useRef(null);
11
11
  const focusPanel = useCallback(()=>{
12
12
  const panel = document.querySelector('[data-slot="nav-panel"]');
@@ -47,7 +47,7 @@ const NavRail = ({ ref, collapsed = false, appeared, className, children, 'data-
47
47
  "data-testid": testId,
48
48
  className: cn(navRailVariants({
49
49
  collapsed
50
- }), false === appeared && 'opacity-0', className),
50
+ }), className),
51
51
  children: children
52
52
  })
53
53
  })
@@ -1,5 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
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 transition-[width,opacity] duration-200 ease-in-out', {
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
4
  collapsed: {
5
5
  true: 'w-[48px]',
@@ -1,6 +1,3 @@
1
- import type { FC, HTMLAttributes, Ref } from 'react';
2
- export interface SplashScreenProps extends HTMLAttributes<HTMLDivElement> {
3
- ref?: Ref<HTMLDivElement>;
4
- visible?: boolean;
5
- }
1
+ import type { FC } from 'react';
2
+ import type { SplashScreenProps } from './types';
6
3
  export declare const SplashScreen: FC<SplashScreenProps>;
@@ -1,57 +1,49 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
2
  import { cn } from "../../utils/cn.js";
4
3
  import { Logo } from "../Logo/index.js";
5
4
  import { Progress } from "../Progress/index.js";
6
- import { splashContentVariants, splashLogoVariants, splashProgressVariants } from "./classes.js";
7
- const SplashScreen = ({ ref, visible = true, className, ...props })=>{
8
- const [phase, setPhase] = useState(visible ? 'enter-start' : 'exited');
9
- useEffect(()=>{
10
- if (visible) {
11
- setPhase('enter-start');
12
- let inner;
13
- const outer = requestAnimationFrame(()=>{
14
- inner = requestAnimationFrame(()=>{
15
- setPhase('entered');
16
- });
17
- });
18
- return ()=>{
19
- cancelAnimationFrame(outer);
20
- cancelAnimationFrame(inner);
21
- };
22
- }
23
- setPhase((prev)=>'exited' === prev || 'enter-start' === prev ? 'exited' : 'exiting');
24
- }, [
25
- visible
26
- ]);
5
+ import { splashContainerVariants, splashContentVariants, splashLogoVariants, splashProgressVariants } from "./classes.js";
6
+ import { SPLASH_PHASES, getContainerStyle } from "./lib.js";
7
+ import { useSplashPhase } from "./useSplashPhase.js";
8
+ const SplashScreen = ({ ref, visible = true, shrinkTarget, className, children, ...props })=>{
9
+ const { phase, childrenRevealed, handleContainerTransitionEnd, handleContentTransitionEnd } = useSplashPhase(visible, shrinkTarget);
27
10
  if ('exited' === phase) return null;
28
- const animPhase = phase;
29
- return /*#__PURE__*/ jsx("div", {
11
+ const containerPhase = phase;
12
+ const contentPhase = phase;
13
+ return /*#__PURE__*/ jsxs("div", {
30
14
  ...props,
31
15
  "data-slot": "splash-screen",
32
16
  ref: ref,
33
- className: "h-full w-full flex items-center justify-center",
34
- children: /*#__PURE__*/ jsxs("div", {
35
- className: cn(splashContentVariants({
36
- phase: animPhase
37
- }), className),
38
- onTransitionEnd: ()=>{
39
- if ('exiting' === phase) setPhase('exited');
40
- },
41
- children: [
42
- /*#__PURE__*/ jsx(Logo, {
43
- className: splashLogoVariants({
44
- phase: animPhase
45
- })
17
+ className: cn(splashContainerVariants({
18
+ phase: containerPhase
19
+ }), className),
20
+ style: getContainerStyle(phase, shrinkTarget),
21
+ onTransitionEnd: handleContainerTransitionEnd,
22
+ children: [
23
+ SPLASH_PHASES[contentPhase] && /*#__PURE__*/ jsxs("div", {
24
+ className: splashContentVariants({
25
+ phase: contentPhase
46
26
  }),
47
- /*#__PURE__*/ jsx(Progress, {
48
- value: null,
49
- className: splashProgressVariants({
50
- phase: animPhase
27
+ onTransitionEnd: handleContentTransitionEnd,
28
+ children: [
29
+ /*#__PURE__*/ jsx(Logo, {
30
+ className: splashLogoVariants({
31
+ phase: contentPhase
32
+ })
33
+ }),
34
+ /*#__PURE__*/ jsx(Progress, {
35
+ value: null,
36
+ className: splashProgressVariants({
37
+ phase: contentPhase
38
+ })
51
39
  })
52
- })
53
- ]
54
- })
40
+ ]
41
+ }),
42
+ 'settled' === phase && children && /*#__PURE__*/ jsx("div", {
43
+ className: cn('h-full w-full transition-opacity duration-300', childrenRevealed ? 'opacity-100' : 'opacity-0'),
44
+ children: children
45
+ })
46
+ ]
55
47
  });
56
48
  };
57
49
  SplashScreen.displayName = 'SplashScreen';
@@ -1,9 +1,12 @@
1
+ export declare const splashContainerVariants: (props?: ({
2
+ phase?: "enter-start" | "entered" | "content-fading" | "shrinking" | "settled" | "exiting" | null | undefined;
3
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
1
4
  export declare const splashContentVariants: (props?: ({
2
- phase?: "enter-start" | "entered" | "exiting" | null | undefined;
5
+ phase?: "enter-start" | "entered" | "content-fading" | "exiting" | null | undefined;
3
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
4
7
  export declare const splashLogoVariants: (props?: ({
5
- phase?: "enter-start" | "entered" | "exiting" | null | undefined;
8
+ phase?: "enter-start" | "entered" | "content-fading" | "exiting" | null | undefined;
6
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
10
  export declare const splashProgressVariants: (props?: ({
8
- phase?: "enter-start" | "entered" | "exiting" | null | undefined;
11
+ phase?: "enter-start" | "entered" | "content-fading" | "exiting" | null | undefined;
9
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -1,9 +1,22 @@
1
1
  import { cva } from "class-variance-authority";
2
+ const splashContainerVariants = cva('flex items-center justify-center', {
3
+ variants: {
4
+ phase: {
5
+ 'enter-start': 'h-full w-full',
6
+ entered: 'h-full w-full',
7
+ 'content-fading': 'h-full w-full',
8
+ shrinking: 'h-full w-full',
9
+ settled: 'overflow-hidden',
10
+ exiting: 'h-full w-full'
11
+ }
12
+ }
13
+ });
2
14
  const splashContentVariants = cva('flex h-max w-max flex-col items-center justify-center gap-12', {
3
15
  variants: {
4
16
  phase: {
5
17
  'enter-start': 'opacity-0',
6
18
  entered: 'opacity-100 transition-opacity duration-500 ease-out',
19
+ 'content-fading': 'opacity-0 transition-opacity duration-300 ease-out',
7
20
  exiting: 'opacity-0 transition-opacity duration-300 ease-out'
8
21
  }
9
22
  }
@@ -13,6 +26,7 @@ const splashLogoVariants = cva('', {
13
26
  phase: {
14
27
  'enter-start': 'translate-y-8',
15
28
  entered: 'translate-y-0 transition-transform duration-500 ease-out',
29
+ 'content-fading': 'translate-y-0',
16
30
  exiting: 'translate-y-0 transition-transform duration-500 ease-out'
17
31
  }
18
32
  }
@@ -22,8 +36,9 @@ const splashProgressVariants = cva('', {
22
36
  phase: {
23
37
  'enter-start': 'translate-y-16',
24
38
  entered: 'translate-y-0 transition-transform duration-500 ease-out',
39
+ 'content-fading': 'translate-y-0',
25
40
  exiting: 'translate-y-0 transition-transform duration-500 ease-out'
26
41
  }
27
42
  }
28
43
  });
29
- export { splashContentVariants, splashLogoVariants, splashProgressVariants };
44
+ export { splashContainerVariants, splashContentVariants, splashLogoVariants, splashProgressVariants };
@@ -1 +1,2 @@
1
- export { SplashScreen, type SplashScreenProps } from './SplashScreen';
1
+ export { SplashScreen } from './SplashScreen';
2
+ export type { SplashScreenProps, SplashScreenShrinkTarget } from './types';
@@ -0,0 +1,4 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { ContentPhase, PhaseType, SplashScreenShrinkTarget } from './types';
3
+ export declare const SPLASH_PHASES: Record<ContentPhase, boolean>;
4
+ export declare function getContainerStyle(phase: PhaseType, shrinkTarget?: SplashScreenShrinkTarget): CSSProperties | undefined;
@@ -0,0 +1,31 @@
1
+ const SPLASH_PHASES = {
2
+ 'enter-start': true,
3
+ entered: true,
4
+ 'content-fading': true,
5
+ exiting: true
6
+ };
7
+ function getContainerStyle(phase, shrinkTarget) {
8
+ if (!shrinkTarget) return;
9
+ const { width, height, borderRadius = 0 } = shrinkTarget;
10
+ switch(phase){
11
+ case 'content-fading':
12
+ return {
13
+ clipPath: 'inset(0 0 round 0px)',
14
+ transition: 'clip-path 500ms ease-in-out'
15
+ };
16
+ case 'shrinking':
17
+ return {
18
+ clipPath: `inset(calc(50% - ${height / 2}px) calc(50% - ${width / 2}px) round ${borderRadius}px)`,
19
+ transition: 'clip-path 500ms ease-in-out'
20
+ };
21
+ case 'settled':
22
+ return {
23
+ width,
24
+ height,
25
+ borderRadius
26
+ };
27
+ default:
28
+ return;
29
+ }
30
+ }
31
+ export { SPLASH_PHASES, getContainerStyle };
@@ -0,0 +1,14 @@
1
+ import type { HTMLAttributes, ReactNode, Ref } from 'react';
2
+ export type ContentPhase = 'enter-start' | 'entered' | 'content-fading' | 'exiting';
3
+ export type PhaseType = ContentPhase | 'shrinking' | 'settled' | 'exited';
4
+ export interface SplashScreenShrinkTarget {
5
+ width: number;
6
+ height: number;
7
+ borderRadius?: number;
8
+ }
9
+ export interface SplashScreenProps extends HTMLAttributes<HTMLDivElement> {
10
+ ref?: Ref<HTMLDivElement>;
11
+ visible?: boolean;
12
+ shrinkTarget?: SplashScreenShrinkTarget;
13
+ children?: ReactNode;
14
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ import type { TransitionEvent } from 'react';
2
+ import type { PhaseType, SplashScreenShrinkTarget } from './types';
3
+ export declare const useSplashPhase: (visible: boolean, shrinkTarget?: SplashScreenShrinkTarget) => {
4
+ phase: PhaseType;
5
+ childrenRevealed: boolean;
6
+ handleContainerTransitionEnd: (e: TransitionEvent<HTMLDivElement>) => void;
7
+ handleContentTransitionEnd: (e: TransitionEvent<HTMLDivElement>) => void;
8
+ };
@@ -0,0 +1,55 @@
1
+ import { useEffect, useState } from "react";
2
+ const doubleRaf = (callback)=>{
3
+ let id2;
4
+ const id1 = requestAnimationFrame(()=>{
5
+ id2 = requestAnimationFrame(callback);
6
+ });
7
+ return ()=>{
8
+ cancelAnimationFrame(id1);
9
+ cancelAnimationFrame(id2);
10
+ };
11
+ };
12
+ const useSplashPhase = (visible, shrinkTarget)=>{
13
+ const [phase, setPhase] = useState(()=>{
14
+ if (visible) return 'enter-start';
15
+ if (shrinkTarget) return 'settled';
16
+ return 'exited';
17
+ });
18
+ const [childrenRevealed, setChildrenRevealed] = useState(!visible && !!shrinkTarget);
19
+ useEffect(()=>{
20
+ if (visible) {
21
+ setPhase('enter-start');
22
+ setChildrenRevealed(false);
23
+ return doubleRaf(()=>setPhase('entered'));
24
+ }
25
+ setPhase((prev)=>{
26
+ if ('exited' === prev || 'enter-start' === prev) return shrinkTarget ? 'settled' : 'exited';
27
+ return shrinkTarget ? 'content-fading' : 'exiting';
28
+ });
29
+ }, [
30
+ visible,
31
+ shrinkTarget
32
+ ]);
33
+ useEffect(()=>{
34
+ if ('settled' !== phase) return;
35
+ return doubleRaf(()=>setChildrenRevealed(true));
36
+ }, [
37
+ phase
38
+ ]);
39
+ const handleContainerTransitionEnd = (e)=>{
40
+ if (e.target !== e.currentTarget) return;
41
+ if ('shrinking' === phase && 'clip-path' === e.propertyName) setPhase('settled');
42
+ };
43
+ const handleContentTransitionEnd = (e)=>{
44
+ e.stopPropagation();
45
+ if ('exiting' === phase) setPhase('exited');
46
+ else if ('content-fading' === phase) setPhase('shrinking');
47
+ };
48
+ return {
49
+ phase,
50
+ childrenRevealed,
51
+ handleContainerTransitionEnd,
52
+ handleContentTransitionEnd
53
+ };
54
+ };
55
+ export { useSplashPhase };
@@ -3,6 +3,5 @@ import { type TestableProps } from '../../utils/testId';
3
3
  export interface TopHeaderProps extends HTMLAttributes<HTMLDivElement>, TestableProps {
4
4
  ref?: Ref<HTMLDivElement>;
5
5
  children?: ReactNode;
6
- appeared?: boolean;
7
6
  }
8
7
  export declare const TopHeader: FC<TopHeaderProps>;
@@ -1,14 +1,14 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { TestIdProvider } from "../../utils/testId.js";
4
- const TopHeader = ({ ref, appeared, className, children, 'data-testid': testId, ...props })=>/*#__PURE__*/ jsx(TestIdProvider, {
4
+ const TopHeader = ({ ref, className, children, 'data-testid': testId, ...props })=>/*#__PURE__*/ jsx(TestIdProvider, {
5
5
  value: testId,
6
6
  children: /*#__PURE__*/ jsx("div", {
7
7
  ...props,
8
8
  ref: ref,
9
9
  "data-slot": "top-header",
10
10
  "data-testid": testId,
11
- className: cn('flex items-center justify-between pl-7 pr-12 py-6', 'transition-opacity duration-200 ease-in-out', false === appeared && 'opacity-0', className),
11
+ className: cn('flex items-center justify-between pl-7 pr-12 py-6', className),
12
12
  children: children
13
13
  })
14
14
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.53.1",
3
- "generatedAt": "2026-06-03T21:17:22.457Z",
3
+ "generatedAt": "2026-06-04T00:14:48.977Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -3711,7 +3711,7 @@
3711
3711
  },
3712
3712
  {
3713
3713
  "name": "WithCard",
3714
- "code": "() => (\n <div className='relative h-full w-full'>\n <AnimatedBackground />\n\n <Card className='absolute top-1/2 left-1/2 -translate-1/2 w-[300px] h-max'>\n <CardHeader>\n <CardTitle>Sign In</CardTitle>\n </CardHeader>\n <CardContent>\n <p className='text-sm text-text-secondary'>\n Decorative background renders behind interactive content.\n </p>\n </CardContent>\n </Card>\n </div>\n)"
3714
+ "code": "() => (\n <div className='relative h-screen w-screen'>\n <AnimatedBackground />\n\n <Card className='absolute top-1/2 left-1/2 -translate-1/2 w-[300px] h-max'>\n <CardHeader>\n <CardTitle>Sign In</CardTitle>\n </CardHeader>\n <CardContent>\n <p className='text-sm text-text-secondary'>\n Decorative background renders behind interactive content.\n </p>\n </CardContent>\n </Card>\n </div>\n)"
3715
3715
  }
3716
3716
  ]
3717
3717
  },
@@ -3719,6 +3719,16 @@
3719
3719
  "name": "AppShell",
3720
3720
  "importPath": "@wallarm-org/design-system/AppShell",
3721
3721
  "props": [
3722
+ {
3723
+ "name": "reveal",
3724
+ "type": "boolean | undefined",
3725
+ "required": false
3726
+ },
3727
+ {
3728
+ "name": "appeared",
3729
+ "type": "boolean | undefined",
3730
+ "required": false
3731
+ },
3722
3732
  {
3723
3733
  "name": "defaultChecked",
3724
3734
  "type": "boolean | undefined",
@@ -4821,7 +4831,7 @@
4821
4831
  "examples": [
4822
4832
  {
4823
4833
  "name": "Basic",
4824
- "code": "() => {\n const pathname = useLocationPathname();\n const activeProduct = deriveProduct(pathname);\n\n const [appeared, setAppeared] = useState(true);\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 appeared={appeared}>\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 ) : (\n <>\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n className='p-4 gap-6 rounded-6'\n >\n <Code size='s' color='secondary'>\n Search Wallarm\n </Code>\n <Kbd size='xsmall'>⌘ K</Kbd>\n </Button>\n\n <TopHeaderSeparator />\n\n <Button variant='ghost' size='small' color='neutral' className='py-4 rounded-6'>\n <Text size='xs' weight='medium'>\n Tenant Name\n </Text>\n <span className='text-text-tertiary mx-[-2px]'>•</span>\n <Code size='s' color='secondary'>\n 12345\n </Code>\n <ChevronUpDown className='!icon-sm' />\n </Button>\n </>\n )}\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant='ghost' size='small' color='neutral' aria-label='Wallarm Updates'>\n <Bell />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Wallarm updates</TooltipContent>\n </Tooltip>\n\n <QuickHelpDropdown />\n </TopHeaderActions>\n </TopHeader>\n </AppShellHeader>\n\n <AppShellRail>\n <NavRail collapsed={collapsed} appeared={appeared}>\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 ? (\n <NavRailSkeleton />\n ) : (\n <>\n <NavRailItem\n icon={CircleDashed}\n label='Edge'\n shortcut={['G', 'E']}\n active={activeProduct === 'edge'}\n onClick={() => navigateToProduct('edge')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='AI Hypervisor'\n shortcut={['G', 'A']}\n active={activeProduct === 'ai-hypervisor'}\n onClick={() => navigateToProduct('ai-hypervisor')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='Infra Discovery'\n shortcut={['G', 'I']}\n active={activeProduct === 'infra-discovery'}\n onClick={() => navigateToProduct('infra-discovery')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='Security Testing'\n shortcut={['G', 'T']}\n active={activeProduct === 'security-testing'}\n onClick={() => navigateToProduct('security-testing')}\n />\n </>\n )}\n </NavRailBody>\n\n <NavRailFooter>\n <NavRailItem\n icon={Settings}\n label='Settings'\n shortcut={['G', 'S']}\n active={activeProduct === 'settings'}\n onClick={() => navigateToProduct('settings')}\n />\n <AccountDropdown\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRailFooter>\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={() => setAppeared(v => !v)}>\n {appeared ? 'Hide menu' : 'Show menu'}\n </Button>\n\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}"
4834
+ "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 [revealKey, setRevealKey] = useState(0);\n const { theme, setTheme } = useTheme();\n const collapsed = sidebarMode === 'adaptive' && activeProduct !== 'home';\n\n return (\n <AppShell key={revealKey} reveal>\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 ) : (\n <>\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n className='p-4 gap-6 rounded-6'\n >\n <Code size='s' color='secondary'>\n Search Wallarm\n </Code>\n <Kbd size='xsmall'>⌘ K</Kbd>\n </Button>\n\n <TopHeaderSeparator />\n\n <Button variant='ghost' size='small' color='neutral' className='py-4 rounded-6'>\n <Text size='xs' weight='medium'>\n Tenant Name\n </Text>\n <span className='text-text-tertiary mx-[-2px]'>•</span>\n <Code size='s' color='secondary'>\n 12345\n </Code>\n <ChevronUpDown className='!icon-sm' />\n </Button>\n </>\n )}\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant='ghost' size='small' color='neutral' aria-label='Wallarm Updates'>\n <Bell />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Wallarm updates</TooltipContent>\n </Tooltip>\n\n <QuickHelpDropdown />\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 ? (\n <NavRailSkeleton />\n ) : (\n <>\n <NavRailItem\n icon={CircleDashed}\n label='Edge'\n shortcut={['G', 'E']}\n active={activeProduct === 'edge'}\n onClick={() => navigateToProduct('edge')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='AI Hypervisor'\n shortcut={['G', 'A']}\n active={activeProduct === 'ai-hypervisor'}\n onClick={() => navigateToProduct('ai-hypervisor')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='Infra Discovery'\n shortcut={['G', 'I']}\n active={activeProduct === 'infra-discovery'}\n onClick={() => navigateToProduct('infra-discovery')}\n />\n <NavRailItem\n icon={CircleDashed}\n label='Security Testing'\n shortcut={['G', 'T']}\n active={activeProduct === 'security-testing'}\n onClick={() => navigateToProduct('security-testing')}\n />\n </>\n )}\n </NavRailBody>\n\n <NavRailFooter>\n <NavRailItem\n icon={Settings}\n label='Settings'\n shortcut={['G', 'S']}\n active={activeProduct === 'settings'}\n onClick={() => navigateToProduct('settings')}\n />\n <AccountDropdown\n sidebarMode={sidebarMode}\n onSidebarModeChange={setSidebarMode}\n theme={theme}\n onThemeChange={setTheme}\n />\n </NavRailFooter>\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\n <Button\n variant='ghost'\n size='small'\n color='neutral'\n onClick={() => setRevealKey(k => k + 1)}\n >\n Replay animation\n </Button>\n </div>\n\n <RemoteForProduct product={activeProduct} />\n </AppShellRemote>\n </AppShell>\n );\n}"
4825
4835
  }
4826
4836
  ]
4827
4837
  },
@@ -28211,11 +28221,6 @@
28211
28221
  "required": false,
28212
28222
  "defaultValue": "false"
28213
28223
  },
28214
- {
28215
- "name": "appeared",
28216
- "type": "boolean | undefined",
28217
- "required": false
28218
- },
28219
28224
  {
28220
28225
  "name": "defaultChecked",
28221
28226
  "type": "boolean | undefined",
@@ -49258,6 +49263,11 @@
49258
49263
  "required": false,
49259
49264
  "defaultValue": "true"
49260
49265
  },
49266
+ {
49267
+ "name": "shrinkTarget",
49268
+ "type": "SplashScreenShrinkTarget | undefined",
49269
+ "required": false
49270
+ },
49261
49271
  {
49262
49272
  "name": "defaultChecked",
49263
49273
  "type": "boolean | undefined",
@@ -49535,6 +49545,9 @@
49535
49545
  "options": [
49536
49546
  "enter-start",
49537
49547
  "entered",
49548
+ "content-fading",
49549
+ "shrinking",
49550
+ "settled",
49538
49551
  "exiting"
49539
49552
  ]
49540
49553
  }
@@ -49548,6 +49561,10 @@
49548
49561
  {
49549
49562
  "name": "Toggle",
49550
49563
  "code": "() => {\n const [visible, setVisible] = useState(true);\n\n return (\n <VStack align='center'>\n <div className='flex h-400 w-400 items-center justify-center'>\n <SplashScreen visible={visible} />\n </div>\n\n <Button type='button' onClick={() => setVisible(v => !v)}>\n {visible ? 'Hide' : 'Show'}\n </Button>\n </VStack>\n );\n}"
49564
+ },
49565
+ {
49566
+ "name": "ShrinkToCard",
49567
+ "code": "() => {\n const [visible, setVisible] = useState(true);\n\n useEffect(() => {\n if (!visible) return;\n\n const timer = window.setTimeout(() => {\n setVisible(false);\n }, 2000);\n\n return () => window.clearTimeout(timer);\n }, [visible]);\n\n return (\n <div className='relative h-screen w-screen'>\n <AnimatedBackground />\n\n <div className='absolute inset-0 flex items-center justify-center'>\n <SplashScreen\n visible={visible}\n shrinkTarget={{ width: 480, height: 600, borderRadius: 12 }}\n className='bg-bg-page-bg shadow-lg'\n >\n <div className='flex h-full flex-col items-center justify-center gap-4 p-8'>\n <h2 className='text-lg font-semibold text-text-primary'>Welcome</h2>\n <p className='text-center text-sm text-text-secondary'>\n Content revealed after splash animation.\n </p>\n <Button type='button' onClick={() => setVisible(true)}>\n Replay\n </Button>\n </div>\n </SplashScreen>\n </div>\n </div>\n );\n}"
49551
49568
  }
49552
49569
  ]
49553
49570
  },
@@ -54727,11 +54744,6 @@
54727
54744
  "name": "TopHeader",
54728
54745
  "importPath": "@wallarm-org/design-system/TopHeader",
54729
54746
  "props": [
54730
- {
54731
- "name": "appeared",
54732
- "type": "boolean | undefined",
54733
- "required": false
54734
- },
54735
54747
  {
54736
54748
  "name": "defaultChecked",
54737
54749
  "type": "boolean | undefined",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "0.54.0-rc-feature-shell.1",
3
+ "version": "0.54.0-rc-feature-shell.2",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",