-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Lukas.J.Han <[email protected]>
- Loading branch information
Showing
5 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React, { useState, useRef } from 'react'; | ||
import { Label } from './Label'; | ||
|
||
const ChevronIcon: React.FC<{ isOpen: boolean }> = ({ isOpen }) => ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
width="24" | ||
height="24" | ||
className={`transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`} | ||
aria-hidden="true" | ||
> | ||
<path | ||
d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
); | ||
|
||
interface AccordionItemProps { | ||
title: string; | ||
children: React.ReactNode; | ||
isOpen: boolean; | ||
onClick: () => void; | ||
} | ||
|
||
const AccordionItem: React.FC<AccordionItemProps> = ({ | ||
title, | ||
children, | ||
isOpen, | ||
onClick, | ||
}) => { | ||
const contentRef = useRef<HTMLDivElement>(null); | ||
const buttonId = `accordion-button-${title.replace(/\s+/g, '-').toLowerCase()}`; | ||
const contentId = `accordion-content-${title.replace(/\s+/g, '-').toLowerCase()}`; | ||
|
||
return ( | ||
<div className="w-full border-b border-gray-20"> | ||
<button | ||
className="w-full text-left py-6 px-6 flex justify-between items-center focus:outline-none focus:ring-2 focus:ring-primary-50" | ||
onClick={onClick} | ||
aria-expanded={isOpen} | ||
aria-controls={contentId} | ||
> | ||
<Label size="l" weight="bold" className="cursor-pointer"> | ||
{title} | ||
</Label> | ||
<span className="ml-6 flex-shrink-0"> | ||
<ChevronIcon isOpen={isOpen} /> | ||
</span> | ||
<span className="sr-only">{isOpen ? '접기' : '펼치기'}</span> | ||
</button> | ||
<div | ||
ref={contentRef} | ||
role="region" | ||
aria-labelledby={buttonId} | ||
className="overflow-hidden transition-all duration-300 ease-in-out" | ||
style={{ | ||
maxHeight: isOpen ? `${contentRef.current?.scrollHeight}px` : '0px', | ||
}} | ||
> | ||
<div className="p-6">{children}</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
interface AccordionProps { | ||
items: Omit<AccordionItemProps, 'isOpen' | 'onClick'>[]; | ||
} | ||
|
||
export const Accordion: React.FC<AccordionProps> = ({ items }) => { | ||
const [openIndex, setOpenIndex] = useState<number | null>(null); | ||
|
||
const handleItemClick = (index: number) => { | ||
setOpenIndex((prevIndex) => (prevIndex === index ? null : index)); | ||
}; | ||
|
||
return ( | ||
<div className="border-t border-gray-20 overflow-hidden"> | ||
{items.map((item, index) => ( | ||
<AccordionItem | ||
key={index} | ||
{...item} | ||
isOpen={openIndex === index} | ||
onClick={() => handleItemClick(index)} | ||
/> | ||
))} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React, { useState, useRef } from 'react'; | ||
import { Label } from './Label'; | ||
|
||
const ChevronIcon: React.FC<{ isOpen: boolean }> = ({ isOpen }) => ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
width="24" | ||
height="24" | ||
className={`transition-transform duration-300 ${isOpen ? 'rotate-90' : ''}`} | ||
aria-hidden="true" | ||
> | ||
<path | ||
d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
); | ||
|
||
interface DisclosureProps { | ||
title: string; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const Disclosure: React.FC<DisclosureProps> = ({ title, children }) => { | ||
const [isOpen, setIsOpen] = useState(false); | ||
const contentRef = useRef<HTMLDivElement>(null); | ||
const buttonId = `disclosure-button-${title.replace(/\s+/g, '-').toLowerCase()}`; | ||
const contentId = `disclosure-content-${title.replace(/\s+/g, '-').toLowerCase()}`; | ||
|
||
const toggleDisclosure = () => { | ||
setIsOpen(!isOpen); | ||
}; | ||
|
||
return ( | ||
<div className="w-full"> | ||
<button | ||
id={buttonId} | ||
className="text-left py-4 flex items-center focus:outline-none focus:ring-2 focus:ring-primary-50" | ||
onClick={toggleDisclosure} | ||
aria-expanded={isOpen} | ||
aria-controls={contentId} | ||
> | ||
<span className="mr-4 flex-shrink-0"> | ||
<ChevronIcon isOpen={isOpen} /> | ||
</span> | ||
<Label size="m" className="cursor-pointer"> | ||
{title} | ||
</Label> | ||
<span className="sr-only">{isOpen ? '접기' : '펼치기'}</span> | ||
</button> | ||
<div | ||
id={contentId} | ||
ref={contentRef} | ||
role="region" | ||
aria-labelledby={buttonId} | ||
className="overflow-hidden transition-all duration-300 ease-in-out pl-10" | ||
style={{ | ||
maxHeight: isOpen ? `${contentRef.current?.scrollHeight}px` : '0px', | ||
}} | ||
> | ||
<div className="py-4">{children}</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { fn } from '@storybook/test'; | ||
import { Accordion } from '../../packages/core/lib'; | ||
|
||
const meta = { | ||
title: 'Components/Accordion', | ||
component: Accordion, | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
items: { | ||
control: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
} satisfies Meta<typeof Accordion>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
items: [ | ||
{ | ||
title: 'Accordion Item 1', | ||
children: 'Accordion Item 1 Content', | ||
}, | ||
{ | ||
title: 'Accordion Item 2', | ||
children: 'Accordion Item 2 Content', | ||
}, | ||
{ | ||
title: 'Accordion Item 3', | ||
children: 'Accordion Item 3 Content', | ||
}, | ||
], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { fn } from '@storybook/test'; | ||
import { Disclosure } from '../../packages/core/lib'; | ||
|
||
const meta = { | ||
title: 'Components/Disclosure', | ||
component: Disclosure, | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
title: { | ||
control: { | ||
type: 'text', | ||
}, | ||
}, | ||
children: { | ||
control: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
} satisfies Meta<typeof Disclosure>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
title: 'Disclosure Title', | ||
children: | ||
'Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content Disclosure Content', | ||
}, | ||
}; |