From 16f37e1a0d40b59cd45bf9ce41579b1516662d5b Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Thu, 5 Dec 2024 16:09:29 +0300 Subject: [PATCH] Feat: Card Stack (#2783) --- src/renderer/app/styles/theme/default.css | 2 + .../ui/TokenBalanceList.tsx | 2 +- src/renderer/pages/Staking/ui/ShardedList.tsx | 2 +- .../shared/ui-kit/CardStack/CardStack.css | 33 ++++++ .../ui-kit/CardStack/CardStack.stories.tsx | 68 ++++++++++++ .../shared/ui-kit/CardStack/CardStack.tsx | 103 ++++++++++++++++++ src/renderer/shared/ui-kit/index.ts | 1 + tailwind.config.ts | 3 +- 8 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/renderer/shared/ui-kit/CardStack/CardStack.css create mode 100644 src/renderer/shared/ui-kit/CardStack/CardStack.stories.tsx create mode 100644 src/renderer/shared/ui-kit/CardStack/CardStack.tsx diff --git a/src/renderer/app/styles/theme/default.css b/src/renderer/app/styles/theme/default.css index 0da123bc39..f15ed81941 100644 --- a/src/renderer/app/styles/theme/default.css +++ b/src/renderer/app/styles/theme/default.css @@ -130,6 +130,8 @@ --input-active-shadow: 0px 0px 0px 2px rgba(36, 99, 235, 0.16); --card-shadow-level2: 0px 2px 2px rgba(69, 69, 137, 0.05), 0px 4px 6px rgba(69, 69, 137, 0.06), inset 0px -0.5px 0px rgba(69, 69, 137, 0.12); + --stack: 0px 3px 4px 0px rgba(69, 69, 137, 0.04); + --stack-hover: 0px 4px 6px 0px rgba(69, 69, 137, 0.06), 0px 2px 2px 0px rgba(69, 69, 137, 0.05); --shadow-1: 0px 3px 4px 0px rgba(69, 69, 137, 0.04), 0px -0.5px 0px 0px rgba(69, 69, 137, 0.12) inset; --shadow-2: 0px 4px 6px 0px rgba(69, 69, 137, 0.06), 0px -0.5px 0px 0px rgba(69, 69, 137, 0.12) inset, 0px 2px 2px 0px rgba(69, 69, 137, 0.05); diff --git a/src/renderer/features/assets/AssetsPortfolioView/ui/TokenBalanceList.tsx b/src/renderer/features/assets/AssetsPortfolioView/ui/TokenBalanceList.tsx index 8ae20b16cb..96256b772c 100644 --- a/src/renderer/features/assets/AssetsPortfolioView/ui/TokenBalanceList.tsx +++ b/src/renderer/features/assets/AssetsPortfolioView/ui/TokenBalanceList.tsx @@ -41,7 +41,7 @@ export const TokenBalanceList = memo(({ asset }: Props) => { const totalBalance = useMemo(() => tokensService.calculateTotalBalance(asset.chains), [asset.chains]); return ( - + +
diff --git a/src/renderer/shared/ui-kit/CardStack/CardStack.css b/src/renderer/shared/ui-kit/CardStack/CardStack.css new file mode 100644 index 0000000000..42be94987b --- /dev/null +++ b/src/renderer/shared/ui-kit/CardStack/CardStack.css @@ -0,0 +1,33 @@ +.card-stack-content { + transition: max-height 0.3s ease-in-out !important; +} + +.card-stack-content[data-state="closed"] { + max-height: 6px; +} + +.card-stack-content[data-state="open"] { + max-height: var(--radix-accordion-content-height); +} + +.card-stack-plate { + transition: max-width 0.3s ease-in-out; +} + +.card-stack-content[data-state="closed"] .card-stack-plate { + max-width: calc(100% - 8px); +} + +.card-stack-content[data-state="open"] .card-stack-plate { + max-width: 100%; +} + +.card-stack-content[data-state="closed"] .card-stack-section { + transition: visibility; + transition-delay: 0.3s; + visibility: hidden; +} + +.card-stack-content[data-state="open"] .card-stack-section { + visibility: visible; +} diff --git a/src/renderer/shared/ui-kit/CardStack/CardStack.stories.tsx b/src/renderer/shared/ui-kit/CardStack/CardStack.stories.tsx new file mode 100644 index 0000000000..2f3f825e4b --- /dev/null +++ b/src/renderer/shared/ui-kit/CardStack/CardStack.stories.tsx @@ -0,0 +1,68 @@ +import { type Meta, type StoryObj } from '@storybook/react'; + +import { Box } from '../Box/Box'; + +import { CardStack } from './CardStack'; + +const meta: Meta = { + component: CardStack, + title: 'Design System/kit/CardStack', +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render(args) { + return ( +
+ + + Hello, open me, please + +
    + {Array.from({ length: 7 }).map((_, index) => ( +
  • + My text + +
  • + ))} +
+
+
+
+
+ ); + }, +}; + +export const StickyTrigger: Story = { + render(args) { + return ( +
+ + + Hello, I'm sticky button + +
    +
      + {Array.from({ length: 7 }).map((_, index) => ( +
    • + My text + +
    • + ))} +
    +
+
+
+
+
+ ); + }, +}; diff --git a/src/renderer/shared/ui-kit/CardStack/CardStack.tsx b/src/renderer/shared/ui-kit/CardStack/CardStack.tsx new file mode 100644 index 0000000000..b748c80586 --- /dev/null +++ b/src/renderer/shared/ui-kit/CardStack/CardStack.tsx @@ -0,0 +1,103 @@ +import * as RadixAccordion from '@radix-ui/react-accordion'; +import { type PropsWithChildren, createContext, useId, useLayoutEffect, useMemo, useRef, useState } from 'react'; + +import { cnTw } from '@/shared/lib/utils'; +import { Icon } from '@/shared/ui'; + +import './CardStack.css'; + +const Context = createContext<{ open: boolean }>({ open: false }); + +type RootProps = PropsWithChildren<{ + initialOpen?: boolean; +}>; + +const Root = ({ initialOpen = false, children }: RootProps) => { + const id = useId(); + const [open, setOpen] = useState(initialOpen); + + const ctx = useMemo(() => ({ open }), [open]); + + return ( + + setOpen(value === id)} + > + {children} + + + ); +}; + +type TriggerProps = PropsWithChildren<{ + sticky?: boolean; +}>; + +const Trigger = ({ sticky, children }: TriggerProps) => { + return ( + +
+ + +
{children}
+
+
+
+ ); +}; + +const Content = ({ children }: PropsWithChildren) => { + const ref = useRef(null); + const contentRef = useRef(null); + + useLayoutEffect(() => { + if (!ref.current || !contentRef.current) return; + + const resizeObserver = new ResizeObserver(() => { + const { clientHeight } = ref.current!; + + contentRef.current!.style.cssText = `--radix-accordion-content-height: ${clientHeight}px;`; + }); + + resizeObserver.observe(ref.current); + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + return ( + +
+
+ {children} +
+ + ); +}; + +export const CardStack = Object.assign(Root, { + Trigger, + Content, +}); diff --git a/src/renderer/shared/ui-kit/index.ts b/src/renderer/shared/ui-kit/index.ts index db0f9aaa6f..240ac16ca5 100644 --- a/src/renderer/shared/ui-kit/index.ts +++ b/src/renderer/shared/ui-kit/index.ts @@ -5,6 +5,7 @@ export { SearchInput } from './SearchInput/SearchInput'; export { ThemeProvider } from './Theme/ThemeProvider'; export { ScrollArea } from './ScrollArea/ScrollArea'; export { Accordion } from './Accordion/Accordion'; +export { CardStack } from './CardStack/CardStack'; export { InputFile } from './InputFile/InputFile'; export { Checkbox } from './Checkbox/Checkbox'; export { Combobox } from './Combobox/Combobox'; diff --git a/tailwind.config.ts b/tailwind.config.ts index 0f261381fe..cc675faab7 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -37,7 +37,8 @@ const tailwindConfig: Config = { 'knob-shadow': 'var(--knob-shadow)', 'input-active-shadow': 'var(--input-active-shadow)', 'card-shadow-level2': 'var(--card-shadow-level2)', - shards: '0px 3px 1px 0px rgba(69, 69, 137, 0.04)', + stack: 'var(--stack)', + 'stack-hover': 'var(--stack-hover)', 'shadow-1': 'var(--shadow-1)', 'shadow-2': 'var(--shadow-2)', },