Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/ui/Accordion/Accordion.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
component: Accordion,
render: (args) => <SandboxEditor>
<div >
<div className='flex space-x-2'>
<div className='flex space-x-2 w-full flex-1'>
<Accordion {...args} />

</div>
Expand All @@ -22,6 +22,8 @@ export const All = {
items: [
{title: 'Section 1', content: 'Content for Section 1'},
{title: 'Section 2', content: 'Content for Section 2'},
{title: 'Section 3', content: 'Content for Section 3'},
{title: 'Section 4', content: 'Content for Section 4'},
// Add more items as needed
],
},
Expand Down
11 changes: 3 additions & 8 deletions src/components/ui/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@ export type AccordionProps = {
}

const Accordion = ({items} : AccordionProps) => {
const [activeIndex, setActiveIndex] = useState<number>(-1);
const handleClick = (index: number) => {
setActiveIndex(activeIndex === index ? -1 : index);
};

return (
<AccordionRoot>
{items.map((item, index) => (
<AccordionItem value={index}>
<AccordionItem value={index} key={index} >
<AccordionHeader>
<AccordionTrigger handleClick={handleClick} index={index} activeIndex={activeIndex} >
<AccordionTrigger >
Item {index+1}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent index={index} activeIndex={activeIndex}>
<AccordionContent>
Comment on lines +18 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Missing Key Property in List Rendering

The map function in JSX should have a key prop for each child element to maintain proper state and avoid potential rendering issues.

-                <AccordionItem value={index}>
+                <AccordionItem key={index} value={index}>
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<AccordionTrigger >
Item {index+1}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent index={index} activeIndex={activeIndex}>
<AccordionContent>
<AccordionItem key={index} value={index}>
<AccordionTrigger >
Item {index+1}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent>

{item.content}
</AccordionContent>
</AccordionItem>
Expand Down
3 changes: 3 additions & 0 deletions src/components/ui/Accordion/contexts/AccordionContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {createContext} from 'react';

export const AccordionContext = createContext({});
3 changes: 3 additions & 0 deletions src/components/ui/Accordion/contexts/AccordionItemContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {createContext} from 'react';

export const AccordionItemContext = createContext({});
36 changes: 20 additions & 16 deletions src/components/ui/Accordion/shards/AccordionContent.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import React from 'react';
// @ts-ignore
import {customClassSwitcher} from '~/core';
import React, {useContext} from 'react';
import {AccordionContext} from '../contexts/AccordionContext';
import {AccordionItemContext} from '../contexts/AccordionItemContext';


type AccordionContentProps = {
children: React.ReactNode;
index: number,
activeIndex: number,
customRootClass? :string
className? :string
};

const AccordionContent: React.FC<AccordionContentProps> = ({children, index, activeIndex, customRootClass}: AccordionContentProps) => {
const rootClass = customClassSwitcher(customRootClass, 'Accordion');
const AccordionContent: React.FC<AccordionContentProps> = ({children, index, activeIndex, className=''}: AccordionContentProps) => {
const {activeItem, rootClass} = useContext(AccordionContext);

const {itemValue} = useContext(AccordionItemContext);

return (
<span className={`${rootClass}-content`}>
<div
id={`content-${index}`}
role="region"
aria-labelledby={`section-${index}`}
hidden={activeIndex !== index}
>
{children}
</div>
</span>
<div
className={`${rootClass}-content ${className}`}
id={`content-${index}`}
role="region"
aria-labelledby={`section-${index}`}
hidden={itemValue !== activeItem}>

{children}

</div>
);
};

Expand Down
10 changes: 4 additions & 6 deletions src/components/ui/Accordion/shards/AccordionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import React from 'react';
// @ts-ignore
import {customClassSwitcher} from '~/core';


export type AccordionHeaderProps = {
children: React.ReactNode;
customHeaderClass?: string;
className?: string;
}

const AccordionHeader: React.FC<AccordionHeaderProps> = ({children, customHeaderClass=''}) => {
const rootClass = customClassSwitcher(customHeaderClass, 'Accordion');
const AccordionHeader: React.FC<AccordionHeaderProps> = ({children, className=''}) => {
return (
<div className={`${rootClass}-header`}>
<div className={className}>
{children}
</div>
);
Expand Down
43 changes: 34 additions & 9 deletions src/components/ui/Accordion/shards/AccordionItem.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import React from 'react';
// @ts-ignore
import {customClassSwitcher} from '~/core';
import React, {useState, useContext, useId, useEffect} from 'react';

import {AccordionContext} from '../contexts/AccordionContext';
import {AccordionItemContext} from '../contexts/AccordionItemContext';

export type AccordionItemProps = {
children: React.ReactNode;
customItemClass?: string;
className?: string;
value?: number;
}

const AccordionItem: React.FC<AccordionItemProps> = ({children, value, customItemClass=''}) => {
const rootClass = customClassSwitcher(customItemClass, 'Accordion');
const AccordionItem: React.FC<AccordionItemProps> = ({children, value, className='', ...props}) => {
const [itemValue, setItemValue] = useState(value);
const {rootClass, activeItem} = useContext(AccordionContext);

const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
if (itemValue === activeItem) {
setIsOpen(true);
} else {
setIsOpen(false);
}
}
, [itemValue, activeItem]);
const id = useId();


return (
<div className={`${rootClass}-item`}>
{children}
</div>
<AccordionItemContext.Provider value={{itemValue, setItemValue}}>
<div
className={`${rootClass}-item ${className}`} {...props}
id={`accordion-data-item-${id}`}
role="region"
aria-labelledby={`accordion-trigger-${id}`}
aria-hidden={!isOpen}
data-state={isOpen ? 'open' : 'closed'}

>
{children}
</div>
</AccordionItemContext.Provider>
);
};

Expand Down
64 changes: 58 additions & 6 deletions src/components/ui/Accordion/shards/AccordionRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,70 @@
import React from 'react';
// @ts-ignore
import React, {useState, useRef} from 'react';

import {customClassSwitcher} from '~/core';
import {AccordionContext} from '../contexts/AccordionContext';

const COMPONENT_NAME = 'Accordion';

export type AccordionRootProps = {
children: React.ReactNode;
customRootClass?: string;
}

const AccordionRoot= ({children, customRootClass}: AccordionRootProps) => {
const rootClass = customClassSwitcher(customRootClass, 'Accordion');
const accordionRef = useRef(null);
const [activeItem, setActiveItem] = useState(null);
const [focusItem, setFocusItem] = useState(null);
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);


const getActiveItemId = () => {
let elem = accordionRef?.current;
// get children that have data-state open
if (focusItem) {
elem = focusItem;
} else {
elem = elem?.querySelector('[data-state="open"]');
}

return elem;
};

const focusNextItem = () => {
const elem = getActiveItemId();
const nextElem = elem.nextElementSibling;
setFocusItem(nextElem);
// get button
const button = nextElem.querySelector('button');
// focus button
button?.focus();
};
const focusPrevItem = () => {
const elem = getActiveItemId();
const prevElem = elem.previousElementSibling;
setFocusItem(prevElem);
// get button
const button = prevElem.querySelector('button');
// focus button
button?.focus();
};

return (
<span className={`${rootClass}-root`}>
{children}
</span>
<AccordionContext.Provider
value={{
rootClass: rootClass,
activeItem,
setActiveItem,
focusNextItem,
focusPrevItem,
focusItem,
setFocusItem,

}}>
<div className={`${rootClass}-root`} ref={accordionRef} >
{children}
</div>
</AccordionContext.Provider>

);
};

Expand Down
45 changes: 30 additions & 15 deletions src/components/ui/Accordion/shards/AccordionTrigger.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import React from 'react';
// @ts-ignore
import {customClassSwitcher} from '~/core';
import React, {useContext, useState} from 'react';
import {AccordionContext} from '../contexts/AccordionContext';
import {AccordionItemContext} from '../contexts/AccordionItemContext';


type AccordionTriggerProps = {
children: React.ReactNode;
customRootClass?: string,
className?: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update Accordion Trigger to Use Context and Fix Button Type

The updates to use AccordionContext and renaming the prop enhance the component's integration and simplicity. However, specify the type attribute for the button to prevent unintended form submissions.

-        <button
+        <button type="button"

Also applies to: 13-27

index: number,
activeIndex: number,
handleClick: (index: number) => void
};

const AccordionTrigger: React.FC<AccordionTriggerProps> = ({children, handleClick, index, activeIndex, customRootClass}) => {
const rootClass = customClassSwitcher(customRootClass, 'Accordion');
const AccordionTrigger: React.FC<AccordionTriggerProps> = ({children, index, activeIndex, className=''}) => {
const {setActiveItem, rootClass, focusNextItem, focusPrevItem, activeItem} = useContext(AccordionContext);

const {itemValue} = useContext(AccordionItemContext);
console.log(activeItem, itemValue);

return (
<span className={`${rootClass}-trigger`}>

<button
onClick={() => handleClick(index)}
aria-expanded={activeIndex === index}
aria-controls={`content-${index}`}
>
{children}
</button>
<button
className={`${rootClass}-trigger ${className}`}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
focusNextItem();
}
if (e.key === 'ArrowUp') {
focusPrevItem();
}
}}
onClick={() => {
setActiveItem(itemValue);
}}
aria-expanded={activeItem === itemValue}
aria-controls={`content-${index}`}
>
Comment on lines +22 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify Button Type to Prevent Default Form Submission

As previously noted, the button inside AccordionTrigger should explicitly specify its type to prevent it from submitting forms if nested within one.

-        <button
+        <button type="button"
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className={`${rootClass}-trigger ${className}`}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
focusNextItem();
}
if (e.key === 'ArrowUp') {
focusPrevItem();
}
}}
onClick={() => {
setActiveItem(itemValue);
}}
aria-expanded={activeItem === itemValue}
aria-controls={`content-${index}`}
>
<button type="button"
className={`${rootClass}-trigger ${className}`}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
focusNextItem();
}
if (e.key === 'ArrowUp') {
focusPrevItem();
}
}}
onClick={() => {
setActiveItem(itemValue);
}}
aria-expanded={activeItem === itemValue}
aria-controls={`content-${index}`}
>
Tools
Biome

[error] 22-37: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

{children}
</button>


</span>
);
};

Expand Down
25 changes: 12 additions & 13 deletions src/components/ui/Progress/shards/ProgressIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@ export default function ProgressIndicator({
}


return (
<div
role="progressbar"
className={`${rootClass}-indicator`}
style={{ transform: `translateX(-${maxValue - value}%)` }}
aria-valuenow={value}
aria-valuemax={maxValue}
aria-valuemin={minValue}
>
{renderLabel?.(value)}
</div>
)

return (
<div
role="progressbar"
className={`${rootClass}-indicator`}
style={{transform: `translateX(-${maxValue - value}%)`}}
aria-valuenow={value}
aria-valuemax={maxValue}
aria-valuemin={minValue}
>
{renderLabel?.(value)}
</div>
);
}
Loading