Skip to content

Commit

Permalink
Feat: Card Stack (#2783)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuul-wq authored Dec 5, 2024
1 parent 47fe070 commit 16f37e1
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/renderer/app/styles/theme/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const TokenBalanceList = memo(({ asset }: Props) => {
const totalBalance = useMemo(() => tokensService.calculateTotalBalance(asset.chains), [asset.chains]);

return (
<Plate className="z-10 border-b-4 border-double p-0 shadow-shards">
<Plate className="z-10 border-b-4 border-double p-0 shadow-stack">
<Accordion>
<Accordion.Button
iconOpened="shelfDown"
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/pages/Staking/ui/ShardedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const ShardedList = ({
);

return (
<Plate className="border-b-4 border-double p-0 shadow-shards">
<Plate className="border-b-4 border-double p-0 shadow-stack">
<Accordion className="w-auto">
<div className="flex rounded-md border-b border-divider px-3 py-2 transition-colors hover:bg-action-background-hover">
<Accordion.Button buttonClass="ml-auto w-auto" iconOpened="shelfDown" iconClosed="shelfRight" />
Expand Down
33 changes: 33 additions & 0 deletions src/renderer/shared/ui-kit/CardStack/CardStack.css
Original file line number Diff line number Diff line change
@@ -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;
}
68 changes: 68 additions & 0 deletions src/renderer/shared/ui-kit/CardStack/CardStack.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type Meta, type StoryObj } from '@storybook/react';

import { Box } from '../Box/Box';

import { CardStack } from './CardStack';

const meta: Meta<typeof CardStack> = {
component: CardStack,
title: 'Design System/kit/CardStack',
};

export default meta;

type Story = StoryObj<typeof CardStack>;

export const Default: Story = {
render(args) {
return (
<div className="flex h-[450px] w-full justify-center bg-gray-100 pt-2">
<Box width="600px">
<CardStack {...args}>
<CardStack.Trigger>Hello, open me, please</CardStack.Trigger>
<CardStack.Content>
<ul className="flex flex-col gap-y-4">
{Array.from({ length: 7 }).map((_, index) => (
<li key={index} className="flex justify-between rounded p-2">
<span>My text</span>
<button type="button" className="rounded-md bg-gray-100 p-1">
Click me
</button>
</li>
))}
</ul>
</CardStack.Content>
</CardStack>
</Box>
</div>
);
},
};

export const StickyTrigger: Story = {
render(args) {
return (
<div className="flex h-[200px] w-full justify-center overflow-auto">
<Box width="300px">
<CardStack {...args}>
<CardStack.Trigger sticky>Hello, I&#39;m sticky button</CardStack.Trigger>
<CardStack.Content>
<ul className="flex flex-col gap-y-4 bg-green-100">
<ul className="flex flex-col gap-y-4">
{Array.from({ length: 7 }).map((_, index) => (
<li key={index} className="flex justify-between rounded p-2">
<span>My text</span>
<button type="button" className="rounded-md bg-gray-100 p-1">
Click me
</button>
</li>
))}
</ul>
</ul>
</CardStack.Content>
</CardStack>
</Box>
</div>
);
},
};
103 changes: 103 additions & 0 deletions src/renderer/shared/ui-kit/CardStack/CardStack.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Context.Provider value={ctx}>
<RadixAccordion.Root
collapsible
type="single"
value={open ? id : ''}
onValueChange={(value) => setOpen(value === id)}
>
<RadixAccordion.Item value={id}>{children}</RadixAccordion.Item>
</RadixAccordion.Root>
</Context.Provider>
);
};

type TriggerProps = PropsWithChildren<{
sticky?: boolean;
}>;

const Trigger = ({ sticky, children }: TriggerProps) => {
return (
<RadixAccordion.Header asChild>
<div className={cnTw('relative z-10 block w-full', sticky && 'sticky top-0 z-10')}>
<RadixAccordion.Trigger
className={cnTw(
'group flex w-full items-center gap-x-2 bg-row-background px-3 py-1',
'shadow-stack hover:shadow-stack-hover focus:shadow-stack-hover data-[state=open]:shadow-none',
'transition-all duration-300 data-[state=closed]:rounded-md data-[state=open]:rounded-t-md',
)}
>
<Icon
className={cnTw(
'transition-all duration-100 group-data-[state=open]:rotate-90',
'shrink-0 text-icon-default group-hover:text-icon-hover group-focus:text-icon-hover',
)}
name="shelfRight"
size={16}
/>
<div className="flex min-w-0 grow">{children}</div>
</RadixAccordion.Trigger>
</div>
</RadixAccordion.Header>
);
};

const Content = ({ children }: PropsWithChildren) => {
const ref = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(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 (
<RadixAccordion.Content forceMount ref={contentRef} className="card-stack-content group relative overflow-hidden">
<div
className={cnTw(
'card-stack-plate absolute left-1/2 top-0 h-full w-full -translate-x-1/2 rounded-b-md bg-white shadow-stack',
'group-data-[state=open]:border-t group-data-[state=open]:border-divider group-data-[state=open]:shadow-none',
)}
/>
<section ref={ref} className="card-stack-section relative">
{children}
</section>
</RadixAccordion.Content>
);
};

export const CardStack = Object.assign(Root, {
Trigger,
Content,
});
1 change: 1 addition & 0 deletions src/renderer/shared/ui-kit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
},
Expand Down

0 comments on commit 16f37e1

Please sign in to comment.