diff --git a/src/components/ui/Accordion/Accordion.tsx b/src/components/ui/Accordion/Accordion.tsx index 6107f2e4..c9baf35b 100644 --- a/src/components/ui/Accordion/Accordion.tsx +++ b/src/components/ui/Accordion/Accordion.tsx @@ -12,15 +12,17 @@ export interface AccordionItemType { export interface AccordionProps { items: AccordionItemType[]; + variant?: string; + color?: string; } -const Accordion = ({ items }: AccordionProps) => { +const Accordion = ({ items, variant = '', color = '' }: AccordionProps) => { return ( - <AccordionRoot> + <AccordionRoot variant={variant} color={color}> {items.map((item, index) => ( <AccordionItem value={index} key={index} > <AccordionHeader> - <AccordionTrigger > + <AccordionTrigger> {item.title} </AccordionTrigger> </AccordionHeader> diff --git a/src/components/ui/Accordion/fragments/AccordionHeader.tsx b/src/components/ui/Accordion/fragments/AccordionHeader.tsx index be02be81..2eeef41d 100644 --- a/src/components/ui/Accordion/fragments/AccordionHeader.tsx +++ b/src/components/ui/Accordion/fragments/AccordionHeader.tsx @@ -1,14 +1,20 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { clsx } from 'clsx'; - +import { AccordionContext } from '../contexts/AccordionContext'; +import { AccordionItemContext } from '../contexts/AccordionItemContext'; export type AccordionHeaderProps = { children: React.ReactNode; className?: string; } const AccordionHeader: React.FC<AccordionHeaderProps> = ({ children, className = '' }) => { + const { setActiveItem, rootClass, focusNextItem, focusPrevItem, activeItem } = useContext(AccordionContext); + const { itemValue, handleBlurEvent, handleClickEvent, handleFocusEvent } = useContext(AccordionItemContext); + + const expanded = activeItem === itemValue; + return ( - <div className={clsx(className)}> + <div className={clsx(`${rootClass}-header`, className)} data-expanded={expanded}> {children} </div> ); diff --git a/src/components/ui/Accordion/fragments/AccordionRoot.tsx b/src/components/ui/Accordion/fragments/AccordionRoot.tsx index 8633f7eb..48a0badd 100644 --- a/src/components/ui/Accordion/fragments/AccordionRoot.tsx +++ b/src/components/ui/Accordion/fragments/AccordionRoot.tsx @@ -9,21 +9,33 @@ const COMPONENT_NAME = 'Accordion'; export type AccordionRootProps = { children: React.ReactNode; customRootClass?: string; + variant?: string | '' + color?: string | '' + loop?: boolean } -const AccordionRoot = ({ children, customRootClass }: AccordionRootProps) => { +const AccordionRoot = ({ children, customRootClass, variant = '', color = '', loop = true }: AccordionRootProps) => { const accordionRef = useRef<HTMLDivElement | null>(null); const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); const [activeItem, setActiveItem] = useState<number | null>(null); const [focusItem, setFocusItem] = useState<Element | null>(null); // stores the id of the item that should be focused + const dataAttributes: Record<string, string> = {}; + if (variant) { + dataAttributes['data-variant'] = variant; + } + + if (color) { + dataAttributes['data-accent-color'] = color; + } + useEffect(() => {}, []); const focusNextItem = () => { if (!accordionRef.current) return; const batches = getAllBatchElements(accordionRef?.current); - const nextItem = getNextBatchItem(batches); + const nextItem = getNextBatchItem(batches, loop); setFocusItem(nextItem); if (nextItem) { const button = nextItem.querySelector('button'); @@ -35,7 +47,7 @@ const AccordionRoot = ({ children, customRootClass }: AccordionRootProps) => { const focusPrevItem = () => { if (!accordionRef.current) return; const batches = getAllBatchElements(accordionRef?.current); - const prevItem = getPrevBatchItem(batches); + const prevItem = getPrevBatchItem(batches, loop); setFocusItem(prevItem); if (prevItem) { const button = prevItem.querySelector('button'); @@ -57,7 +69,7 @@ const AccordionRoot = ({ children, customRootClass }: AccordionRootProps) => { accordionRef }}> - <div className={clsx(`${rootClass}-root`)} ref={accordionRef}> + <div className={clsx(`${rootClass}-root`)} ref={accordionRef} {...dataAttributes} > {children} </div> </AccordionContext.Provider> diff --git a/src/components/ui/Accordion/fragments/AccordionTrigger.tsx b/src/components/ui/Accordion/fragments/AccordionTrigger.tsx index 4d8f82f9..0db7fab2 100644 --- a/src/components/ui/Accordion/fragments/AccordionTrigger.tsx +++ b/src/components/ui/Accordion/fragments/AccordionTrigger.tsx @@ -29,7 +29,6 @@ const AccordionTrigger: React.FC<AccordionTriggerProps> = ({ children, index, cl }; return ( - <button type="button" className={clsx(`${rootClass}-trigger`, className)} diff --git a/src/components/ui/Accordion/stories/Accordion.stories.tsx b/src/components/ui/Accordion/stories/Accordion.stories.tsx index 505c8aad..8ca7c5b0 100644 --- a/src/components/ui/Accordion/stories/Accordion.stories.tsx +++ b/src/components/ui/Accordion/stories/Accordion.stories.tsx @@ -1,8 +1,51 @@ -import Accordion from '../Accordion'; +import Accordion, { AccordionProps } from '../Accordion'; import SandboxEditor from '~/components/tools/SandboxEditor/SandboxEditor'; import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; +const items = [ + { + title: 'The Matrix (1999)', + content: <div> + <ul> + <li> Summary: A hacker discovers the true nature of reality and his role in the war against its controllers.</li> + <li>Key Characters: Neo, Morpheus, Trinity, Agent Smith</li> + <li>Memorable Quote: "There is no spoon."</li> + </ul> + </div> + }, + { + title: 'The Dark Knight (2008)', + content: <div> + <ul> + <li> Summary: Batman faces his greatest challenge yet as the Joker wreaks havoc on Gotham City.</li> + <li>Key Characters: Batman, Joker, Harvey Dent, Alfred</li> + <li>Memorable Quote: "Why so serious?"</li> + </ul> + </div> + }, + { + title: 'Inception (2010)', + content: <div> + <ul> + <li> Summary: A thief who enters people's dreams to steal their secrets must plant an idea in someone's mind.</li> + <li>Key Characters: Cobb, Ariadne, Mal, Saito</li> + <li>Memorable Quote: "You mustn't be afraid to dream a little bigger, darling."</li> + </ul> + </div> + }, + { + title: 'The Shawshank Redemption (1994)', + content: <div> + <ul> + <li> Summary: A banker is wrongly convicted</li> + <li>Key Characters: Andy Dufresne, Red, Warden Norton, Tommy</li> + <li>Memorable Quote: "Get busy living or get busy dying."</li> + </ul> + </div> + } +]; + // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export const meta: Meta<typeof Accordion> = { title: 'Components/Accordion', @@ -24,47 +67,21 @@ type Story = StoryObj<typeof Accordion>; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const All: Story = { args: { - items: [ - { - title: 'The Matrix (1999)', - content: <div> - <ul> - <li> Summary: A hacker discovers the true nature of reality and his role in the war against its controllers.</li> - <li>Key Characters: Neo, Morpheus, Trinity, Agent Smith</li> - <li>Memorable Quote: "There is no spoon."</li> - </ul> - </div> - }, - { - title: 'The Dark Knight (2008)', - content: <div> - <ul> - <li> Summary: Batman faces his greatest challenge yet as the Joker wreaks havoc on Gotham City.</li> - <li>Key Characters: Batman, Joker, Harvey Dent, Alfred</li> - <li>Memorable Quote: "Why so serious?"</li> - </ul> - </div> - }, - { - title: 'Inception (2010)', - content: <div> - <ul> - <li> Summary: A thief who enters people's dreams to steal their secrets must plant an idea in someone's mind.</li> - <li>Key Characters: Cobb, Ariadne, Mal, Saito</li> - <li>Memorable Quote: "You mustn't be afraid to dream a little bigger, darling."</li> - </ul> - </div> - }, - { - title: 'The Shawshank Redemption (1994)', - content: <div> - <ul> - <li> Summary: A banker is wrongly convicted</li> - <li>Key Characters: Andy Dufresne, Red, Warden Norton, Tommy</li> - <li>Memorable Quote: "Get busy living or get busy dying."</li> - </ul> - </div> - } - ] + items, + variant: 'solid' + } +}; + +export const Outline: Story = { + args: { + items, + variant: 'outline' + } +}; + +export const Color: Story = { + args: { + items, + color: 'blue' } }; diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx index a75344e4..a2f9da3e 100644 --- a/src/components/ui/Button/Button.tsx +++ b/src/components/ui/Button/Button.tsx @@ -10,10 +10,11 @@ const COMPONENT_NAME = 'Button'; export type ButtonProps = { customRootClass?: string; variant?: string; + color?: string; size?:string; } & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & PropsWithChildren -const Button = ({ children, type = 'button', customRootClass = '', className = '', variant = '', size = '', ...props }: ButtonProps) => { +const Button = ({ children, type = 'button', customRootClass = '', className = '', variant = '', size = '', color = '', ...props }: ButtonProps) => { const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); // apply data attribute for accent color // apply attribute only if color is present @@ -27,6 +28,10 @@ const Button = ({ children, type = 'button', customRootClass = '', className = ' data_attributes['data-button-size'] = size; } + if (color) { + data_attributes['data-accent-color'] = color; + } + return ( <ButtonPrimitive type={type} diff --git a/src/components/ui/Button/stories/Button.stories.js b/src/components/ui/Button/stories/Button.stories.js index b9592111..75214ab3 100644 --- a/src/components/ui/Button/stories/Button.stories.js +++ b/src/components/ui/Button/stories/Button.stories.js @@ -37,7 +37,7 @@ export default { }; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args -export const All = { +export const AllVariants = { args: { className: '' } @@ -65,3 +65,22 @@ export const Size = (args) => { </div> </SandboxEditor>; }; + +export const Color = (args) => { + return <SandboxEditor> + <div className='flex items-center space-x-[40px]'> + <div> + <p>Colored Button</p> + <Button color='red' > + <div>{BUTTON_TEXT} </div> <ArrowIcon /> + </Button> + </div> + <div> + <p>Theme Button</p> + <Button> + <div>{BUTTON_TEXT} </div> <ArrowIcon /> + </Button> + </div> + </div> + </SandboxEditor>; +}; diff --git a/src/components/ui/Code/tests/Code.test.tsx b/src/components/ui/Code/tests/Code.test.tsx index 2524b881..3a03926e 100644 --- a/src/components/ui/Code/tests/Code.test.tsx +++ b/src/components/ui/Code/tests/Code.test.tsx @@ -8,6 +8,6 @@ describe('Code Component', () => { const codeElement = container.querySelector('code'); expect(codeElement).toBeInTheDocument(); - expect(codeElement).toHaveTextContent("console.log('Hello world!');"); + expect(codeElement).toHaveTextContent('console.log(\'Hello world!\');'); }); -}) +}); diff --git a/src/components/ui/Em/tests/Em.test.js b/src/components/ui/Em/tests/Em.test.js index bac38bd3..68c72b59 100644 --- a/src/components/ui/Em/tests/Em.test.js +++ b/src/components/ui/Em/tests/Em.test.js @@ -4,32 +4,32 @@ import Em from '../Em'; describe('Em', () => { test('renders Em component', () => { - render(<>Welcome to <Em>RadUI</Em></>); - expect(screen.getByText('RadUI')).toBeInTheDocument(); - }); + render(<>Welcome to <Em>RadUI</Em></>); + expect(screen.getByText('RadUI')).toBeInTheDocument(); + }); test('renders Em tag with valid text', () => { - render(<>Welcome to <Em>RadUI</Em></>); - expect(screen.getByText('RadUI').tagName.toLowerCase()).toBe('em'); - }); - + render(<>Welcome to <Em>RadUI</Em></>); + expect(screen.getByText('RadUI').tagName.toLowerCase()).toBe('em'); + }); + test('renders custom classes correctly', () => { - render(<>Welcome to <Em className="custom-class">RadUI</Em></>); - expect(screen.getByText('RadUI')).toHaveClass('custom-class'); - }); - + render(<>Welcome to <Em className="custom-class">RadUI</Em></>); + expect(screen.getByText('RadUI')).toHaveClass('custom-class'); + }); + test('renders custom styles correctly', () => { - render(<>Welcome to <Em style={{ color: 'red' }}>RadUI</Em></>); - expect(screen.getByText('RadUI')).toHaveStyle('color: red'); - }); - + render(<>Welcome to <Em style={{ color: 'red' }}>RadUI</Em></>); + expect(screen.getByText('RadUI')).toHaveStyle('color: red'); + }); + test('renders custom id correctly', () => { - render(<>Welcome to <Em id="em-id">RadUI</Em></>); - expect(screen.getByText('RadUI')).toHaveAttribute('id', 'em-id'); - }); - + render(<>Welcome to <Em id="em-id">RadUI</Em></>); + expect(screen.getByText('RadUI')).toHaveAttribute('id', 'em-id'); + }); + test('renders custom data attribute correctly', () => { - render(<>Welcome to <Em data-testid="em-data">RadUI</Em></>); - expect(screen.getByText('RadUI')).toHaveAttribute('data-testid', 'em-data'); - }); -}); \ No newline at end of file + render(<>Welcome to <Em data-testid="em-data">RadUI</Em></>); + expect(screen.getByText('RadUI')).toHaveAttribute('data-testid', 'em-data'); + }); +}); diff --git a/src/components/ui/Table/tests/Table.test.tsx b/src/components/ui/Table/tests/Table.test.tsx index 97352601..75b00f6e 100644 --- a/src/components/ui/Table/tests/Table.test.tsx +++ b/src/components/ui/Table/tests/Table.test.tsx @@ -8,14 +8,14 @@ describe('Table Component', () => { { id: 1, fullName: 'John Smith', age: 23, isIntern: 'No' }, { id: 2, fullName: 'Anna Donie', age: 35, isIntern: 'Yes' }, { id: 3, fullName: 'Hannah Brown', age: 20, isIntern: 'Yes' }, - { id: 4, fullName: 'Johnathan White Jr', age: 36, isIntern: 'No' }, + { id: 4, fullName: 'Johnathan White Jr', age: 36, isIntern: 'No' } ]; const employeeKey = [ { key: 'id', name: 'Employee Id' }, { key: 'fullName', name: 'Full Name' }, { key: 'age', name: 'Age' }, - { key: 'isIntern', name: 'In Internship' }, + { key: 'isIntern', name: 'In Internship' } ]; let result: RenderResult; diff --git a/styles/themes/components/accordion.scss b/styles/themes/components/accordion.scss index e19c51cb..6c82c6e2 100644 --- a/styles/themes/components/accordion.scss +++ b/styles/themes/components/accordion.scss @@ -1,48 +1,118 @@ -.rad-ui-accordion-root{ - width: 100%; - border: 1px solid var(--rad-ui-color-gray-1000); - border-radius: 4px; +.rad-ui-accordion-root { + border-radius: 2px; overflow: hidden; + width: 100%; .rad-ui-accordion-item { - background-color: var(--rad-ui-color-gray-50); - // not last child - &:not(:last-child){ - border-bottom: 1px solid var(--rad-ui-color-gray-1000); - } + background-color: var(--rad-ui-color-accent-50); + border: none; + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + margin-bottom: 12px; + + border-radius: 10px; + overflow: hidden; + border: 1px solid var(--rad-ui-color-accent-600); + border-top: 4px solid var(--rad-ui-color-accent-700); - &:focus{ + + &:focus { outline: none; border: none; - color:black; } - &:focus-within{ - color: var(--rad-ui-color-gray-1000); + + &:focus-within { + border: 1px solid var(--rad-ui-color-accent-900); + border-top: 4px solid var(--rad-ui-color-accent-1000); + color: var(--rad-ui-color-accent-900); } - .rad-ui-accordion-trigger{ - width: 100%; - text-align: left; - padding:16px; - font-size: 1em; - font-weight: 600; - color: var(--rad-ui-color-gray-1000); - background-color: var(--rad-ui-color-accent-100); - - &:focus{ - outline: none; + .rad-ui-accordion-header { + + // and if data expanded is true, add bottom border + &[data-expanded="true"] { + border-bottom: 1px solid var(--rad-ui-color-accent-700); color: var(--rad-ui-color-accent-900); - + } + + .rad-ui-accordion-trigger { + background-color: var(--rad-ui-color-accent-50); + padding: 12px 16px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + &:focus { + outline: none; + } } } .rad-ui-accordion-content { - padding: 16px; - font-size: 1em; - background-color: var(--rad-ui-color-gray-100); - color: var(--rad-ui-color-gray-1000); - border-top: 1px solid var(--rad-ui-color-gray-1000); + background-color: var(--rad-ui-color-accent-50); + padding: 12px 16px; + color: var(--rad-ui-color-gray-950); + } + } + + + &[data-variant='outline'] { + width: 100%; + + .rad-ui-accordion-item { + border: none; + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + margin-bottom: 12px; + + border-radius: 10px; + overflow: hidden; + border: 1px solid var(--rad-ui-color-accent-600); + + + &:focus { + outline: none; + border: none; + } + + &:focus-within { + border: 1px solid var(--rad-ui-color-accent-900); + color: var(--rad-ui-color-accent-900); + } + + .rad-ui-accordion-header { + + // and if data expanded is true, add bottom border + &[data-expanded="true"] { + border-bottom: 1px solid var(--rad-ui-color-accent-700); + color: var(--rad-ui-color-accent-900); + } + + .rad-ui-accordion-trigger { + padding: 12px 16px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + &:focus { + outline: none; + } + } + } + + .rad-ui-accordion-content { + padding: 12px 16px; + color: var(--rad-ui-color-gray-950); + font-weight: 400; + font-size: 0.875rem; + } } } } \ No newline at end of file