diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts
index 9af87dbdec2..a166e5ddacc 100644
--- a/packages/ui/.storybook/main.ts
+++ b/packages/ui/.storybook/main.ts
@@ -9,7 +9,7 @@ const config: StorybookConfig = {
// // TODO: Temporally Import one by one story and fix build issue.
// // After all stories are cleaned, revert to wildcard import.
stories: [
- // '../src/components/Accordion/Accordion.stories.tsx',
+ '../src/components/Accordion/Accordion.stories.tsx',
// '../src/components/Alert/Alert.stories.tsx',
// '../src/components/Avatar/Avatar.stories.tsx',
'../src/components/Button/Button.stories.tsx',
diff --git a/packages/ui/src/components/Accordion/Accordion-old.stories.tsx b/packages/ui/src/components/Accordion/Accordion-old.stories.tsx
new file mode 100644
index 00000000000..e11fc9edc0c
--- /dev/null
+++ b/packages/ui/src/components/Accordion/Accordion-old.stories.tsx
@@ -0,0 +1,166 @@
+import React from 'react'
+
+import Accordion, { AccordionProps } from './Accordion'
+import { Badge } from '../Badge'
+
+export default {
+ title: 'Displays/Accordion',
+ component: Accordion,
+}
+
+export const OneItem = (args: AccordionProps) => (
+
+
+ Title of the thing
+ Test badge
+ >
+ }
+ id="first"
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+
+)
+
+export const MultipleItems = (args: AccordionProps) => (
+
+ Title of the thing
+ }
+ id={'1'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'2'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'3'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'4'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+
+)
+
+export const Bordered = (args: AccordionProps) => (
+
+ Title of the thing
+ }
+ id={'1'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'2'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'3'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+ Title of the thing
+ }
+ id={'4'}
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+
+)
+
+Bordered.args = {
+ type: 'bordered',
+}
+
+export const LeftAlignedChevron = (args: AccordionProps) => (
+
+
+ First item
+ Test badge
+ >
+ }
+ id="first"
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+
+ Second item
+ >
+ }
+ id="second"
+ >
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
+
+
+
+)
+
+// export const withDefaultActive = Multiple.bind({})
+// withDefaultActive.args = {
+// defaultActiveId: [1],
+// }
+
+// export const withIconLeft = Multiple.bind({})
+// withIconLeft.args = {
+// iconPosition: 'left',
+// }
+
+// export const withCustomIcon = Multiple.bind({})
+// withCustomIcon.args = {
+// icon: ,
+// }
diff --git a/packages/ui/src/components/Accordion/Accordion-old.tsx b/packages/ui/src/components/Accordion/Accordion-old.tsx
new file mode 100644
index 00000000000..524bf7b9de9
--- /dev/null
+++ b/packages/ui/src/components/Accordion/Accordion-old.tsx
@@ -0,0 +1,137 @@
+import React, { createContext, useContext, useState } from 'react'
+
+import styleHandler from '../../lib/theme/styleHandler'
+
+import * as RadixAccordion from '@radix-ui/react-accordion'
+import { IconChevronDown } from '../Icon/icons/IconChevronDown'
+import { IconChevronUp } from '../Icon/icons/IconChevronUp'
+
+type Type = 'default' | 'bordered'
+type Size = 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
+type Align = 'left' | 'right'
+
+interface ContextValue {
+ bordered?: boolean
+ type: Type
+ justified: Boolean
+ chevronAlign: Align
+}
+
+const AccordionContext = createContext({
+ chevronAlign: 'left',
+ justified: true,
+ type: 'default',
+})
+
+export interface AccordionProps {
+ children?: React.ReactNode
+ className?: string
+
+ onChange?: (item: string | string[]) => void
+ openBehaviour: 'single' | 'multiple'
+ type?: Type
+ defaultValue?: string | string[] | undefined
+ justified?: Boolean
+ chevronAlign?: Align
+}
+
+function Accordion({
+ children,
+ className,
+ onChange,
+ openBehaviour = 'multiple',
+ type = 'default',
+ defaultValue = undefined,
+ justified = false,
+ chevronAlign = 'left',
+}: AccordionProps) {
+ const __styles = styleHandler('accordion')
+
+ let containerClasses = [__styles.variants[type].base]
+
+ if (className) {
+ containerClasses.push(className)
+ }
+
+ const contextValue = {
+ chevronAlign,
+ justified,
+ type,
+ defaultValue,
+ }
+
+ function handleOnChange(e: string | string[]) {
+ if (onChange) onChange(e)
+ const value = e == typeof String ? e.split(' ') : e
+ // setCurrentItems(e)
+ // console.log('about to change state')
+ // currentItems = e
+ // console.log('currentItems', currentItems)
+ }
+
+ return (
+ <>
+
+ {children}
+
+ }
+ >
+ >
+ )
+}
+
+interface ItemProps {
+ children?: React.ReactNode
+ className?: string
+ header: React.ReactNode
+ id: string
+ icon?: React.ReactNode
+ disabled?: boolean
+}
+
+export function Item({ children, className, header, id, disabled }: ItemProps) {
+ const __styles = styleHandler('accordion')
+ const [open, setOpen] = useState(false)
+
+ const { type, justified, chevronAlign } = useContext(AccordionContext)
+
+ let triggerClasses = [__styles.variants[type].trigger]
+ if (justified) triggerClasses.push(__styles.justified)
+ if (className) triggerClasses.push(className)
+
+ let chevronClasses = [__styles.chevron.base, __styles.chevron.align[chevronAlign]]
+
+ if (open && !disabled) {
+ chevronClasses.unshift('!rotate-180')
+ }
+
+ return (
+ {
+ setOpen(!open)
+ }}
+ >
+
+ {header}
+ {!disabled && (
+
+ )}
+
+
+ {children}
+
+
+ )
+}
+
+Accordion.Item = Item
+export default Accordion
diff --git a/packages/ui/src/components/Accordion/Accordion.stories.tsx b/packages/ui/src/components/Accordion/Accordion.stories.tsx
index 613caccc178..25886206394 100644
--- a/packages/ui/src/components/Accordion/Accordion.stories.tsx
+++ b/packages/ui/src/components/Accordion/Accordion.stories.tsx
@@ -1,199 +1,79 @@
-import React from 'react'
+// Button.stories.ts|tsx
-import Accordion, { AccordionProps } from './Accordion'
+import type { Meta, StoryObj } from '@storybook/react'
+
+import { Accordion, AccordionItem } from './Accordion'
import { Badge } from '../Badge'
-export default {
- title: 'Displays/Accordion',
+const meta: Meta = {
+ /* 👇 The title prop is optional.
+ * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
+ * to learn how to generate automatic titles
+ */
+ title: 'General/Accordion',
component: Accordion,
}
-export const OneItem = (args: AccordionProps) => (
-
-
-
- Title of the thing
-
- Test badge
- >
- }
- id="first"
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
-)
+export default meta
-export const MultipleItems = (args: AccordionProps) => (
-
-
- Title of the thing
-
- }
- id={'1'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'2'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'3'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'4'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
-)
+type Story = StoryObj
-export const Bordered = (args: AccordionProps) => (
-
-
- Title of the thing
-
- }
- id={'1'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'2'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'3'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
- Title of the thing
-
- }
- id={'4'}
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
-)
+/*
+ *👇 Render functions are a framework specific feature to allow you control on how the component renders.
+ * See https://storybook.js.org/docs/react/api/csf
+ * to learn how to use render functions.
+ */
-Bordered.args = {
- type: 'bordered',
+export const Default = {
+ args: {
+ type: 'default',
+ },
+ render: ({ children, type }: any) => (
+
+
+
+ Title of the thing
+
+ Yes. It adheres to the WAI-ARIA design pattern.
+
+
+ Is it styled?
+
+ Yes. It comes with default styles that matches the other components' aesthetic.
+
+
+
+ Is it animated?
+
+ Yes. It's animated by default, but you can disable it if you prefer.
+
+
+
+ ),
}
-export const LeftAlignedChevron = (args: AccordionProps) => (
-
-
-
- First item
-
- Test badge
- >
- }
- id="first"
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
-
- Second item
-
- >
- }
- id="second"
- >
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
- amet labore.
-
-
-
-)
-
-// export const withDefaultActive = Multiple.bind({})
-// withDefaultActive.args = {
-// defaultActiveId: [1],
-// }
-
-// export const withIconLeft = Multiple.bind({})
-// withIconLeft.args = {
-// iconPosition: 'left',
-// }
-
-// export const withCustomIcon = Multiple.bind({})
-// withCustomIcon.args = {
-// icon: ,
-// }
+export const Bordered = {
+ args: {
+ type: 'bordered',
+ },
+ render: ({ children, type }: any) => (
+
+
+ Is it accessible?
+ Yes. It adheres to the WAI-ARIA design pattern.
+
+
+ Is it styled?
+
+ Yes. It comes with default styles that matches the other components' aesthetic.
+
+
+
+ Is it animated?
+
+ Yes. It's animated by default, but you can disable it if you prefer.
+
+
+
+ ),
+}
diff --git a/packages/ui/src/components/Accordion/Accordion.tsx b/packages/ui/src/components/Accordion/Accordion.tsx
index 7488ee5666c..80a8cbd7d3e 100644
--- a/packages/ui/src/components/Accordion/Accordion.tsx
+++ b/packages/ui/src/components/Accordion/Accordion.tsx
@@ -1,142 +1,234 @@
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { cn } from '@ui/lib/utils'
+import { VariantProps, cva, cx } from 'class-variance-authority'
+import { ChevronDown } from 'lucide-react'
import React, { createContext, useContext, useState } from 'react'
-import styleHandler from '../../lib/theme/styleHandler'
-
-import * as RadixAccordion from '@radix-ui/react-accordion'
-import { IconChevronDown } from '../Icon/icons/IconChevronDown'
-import { IconChevronUp } from '../Icon/icons/IconChevronUp'
-
-type Type = 'default' | 'bordered'
-type Size = 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
-type Align = 'left' | 'right'
-
-interface ContextValue {
- bordered?: boolean
- type: Type
- justified: Boolean
- chevronAlign: Align
-}
-
-const AccordionContext = createContext({
- chevronAlign: 'left',
- justified: true,
- type: 'default',
+export type AccordionRootVariantsProps = VariantProps
+const accordionRootVariants = cva('flex flex-col', {
+ variants: {
+ type: {
+ default: 'space-y-3',
+ bordered: '-space-y-px',
+ },
+ defaultVariants: {
+ type: 'default',
+ },
+ },
})
-export interface AccordionProps {
- children?: React.ReactNode
- className?: string
- defaultActiveId?: (string | number)[]
- icon?: React.ReactNode
- iconPosition?: Align
- onChange?: (item: string | string[]) => void
+export type AccordionItemVariantsProps = VariantProps
+const accordionItemVariants = cva(
+ 'group first:rounded-tl-md first:rounded-tr-md last:rounded-bl-md last:rounded-br-md',
+ {
+ variants: {
+ type: {
+ default: 'overflow-hidden',
+ bordered: 'border border-scale-700',
+ },
+ justified: {
+ true: 'justify-between',
+ false: '',
+ },
+ },
+ }
+)
+
+const accordionTriggerVariants = cva(
+ `flex flex-row items-center w-full text-left cursor-pointer focus-visible:ring-1 focus-visible:z-10 ring-scale-1100
+ justify-between transition-all hover:underline [&[data-state=open]>svg]:rotate-180`,
+ {
+ variants: {
+ type: {
+ default: 'gap-3',
+ bordered: `
+ px-6 py-4
+ font-medium text-base bg-transparent
+ transition-colors hover:bg-scale-200
+ overflow-hidden
+ group-first:rounded-tl-md group-first:rounded-tr-md
+ group-last:rounded-bl-md group-last:rounded-br-md`,
+ },
+ },
+ }
+)
+
+const accordionContentVariants = cva('pb-4 pt-0', {
+ variants: {
+ type: {
+ default: '',
+ bordered: 'px-6',
+ },
+ },
+})
+
+type Type = AccordionRootVariantsProps['type']
+type Align = 'left' | 'right' | undefined
+
+interface ContextValue {
+ chevronAlign: Align
+ justified: boolean
+ type: Type
+ defaultValue: AccordionPrimitive.AccordionImplSingleProps['defaultValue']
+}
+
+interface AdditionalProps {
openBehaviour: 'single' | 'multiple'
+ variant?: Type
+ // Add other additional props here
+ onChange?: (item: string | string[]) => void
type?: Type
- size?: Size
- defaultValue?: string | string[] | undefined
- justified?: Boolean
+ justified?: AccordionItemVariantsProps['justified']
chevronAlign?: Align
}
-function Accordion({
- children,
- className,
- onChange,
- openBehaviour = 'multiple',
- type = 'default',
- defaultValue = undefined,
- justified = false,
- chevronAlign = 'left',
-}: AccordionProps) {
- const __styles = styleHandler('accordion')
-
- let containerClasses = [__styles.variants[type].base]
-
- if (className) {
- containerClasses.push(className)
- }
-
- const contextValue = {
- chevronAlign,
- justified,
- type,
- defaultValue,
- }
-
- function handleOnChange(e: string | string[]) {
- if (onChange) onChange(e)
- const value = e == typeof String ? e.split(' ') : e
- // setCurrentItems(e)
- // console.log('about to change state')
- // currentItems = e
- // console.log('currentItems', currentItems)
- }
-
- return (
- <>
- {/* //
- @ts-expect-error */}
-
- {children}
-
- }
- >
- >
- )
+interface AccordionComponent
+ extends React.ForwardRefExoticComponent<
+ ExtendedAccordionProps & React.RefAttributes
+ > {
+ Item: typeof AccordionItem
+ Content: typeof AccordionContent
+ Trigger: typeof AccordionTrigger
}
-interface ItemProps {
- children?: React.ReactNode
- className?: string
- header: React.ReactNode
- id: string
- icon?: React.ReactNode
- disabled?: boolean
+export interface AccordionSingleProps extends AccordionPrimitive.AccordionImplSingleProps {
+ openBehaviour: 'single'
+ value?: string
+ // Add other props specific to single accordion
}
-export function Item({ children, className, header, id, disabled }: ItemProps) {
- const __styles = styleHandler('accordion')
+export interface AccordionMultipleProps extends AccordionPrimitive.AccordionImplMultipleProps {
+ openBehaviour: 'multiple'
+ value?: string[]
+ // Add other props specific to multiple accordion
+}
+
+type ExtendedAccordionProps = (AccordionSingleProps | AccordionMultipleProps) & AdditionalProps
+
+const AccordionContext = createContext({
+ chevronAlign: 'left',
+ justified: false, // Set the default value to false
+ type: undefined,
+ defaultValue: undefined,
+})
+
+const Accordion: AccordionComponent = React.forwardRef(
+ ({ openBehaviour, type, chevronAlign, justified, defaultValue, ...props }, ref) => {
+ function handleOnChange(e: string | string[]) {
+ if (props.onChange) props.onChange(e)
+ }
+
+ const contextValue = {
+ chevronAlign,
+ justified: justified || false, // Provide a default value of false
+ type: type || 'default', // Provide a default value for the type
+ defaultValue: Array.isArray(defaultValue) ? defaultValue.join('') : defaultValue, // Convert array to string
+ }
+
+ if (openBehaviour === 'single') {
+ const { value, children, className, ...singleProps } = props as AccordionSingleProps
+ return (
+
+ {children}
+
+ }
+ {...singleProps}
+ />
+ )
+ } else {
+ const { value, children, className, ...multipleProps } = props as AccordionMultipleProps
+ return (
+
+ {props.children}
+
+ }
+ {...multipleProps}
+ />
+ )
+ }
+ }
+) as AccordionComponent
+
+export interface ItemAdditionalProps
+ extends React.ComponentPropsWithoutRef {}
+
+export const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
const [open, setOpen] = useState(false)
- const { type, justified, chevronAlign } = useContext(AccordionContext)
-
- let triggerClasses = [__styles.variants[type].trigger]
- if (justified) triggerClasses.push(__styles.justified)
- if (className) triggerClasses.push(className)
-
- let chevronClasses = [__styles.chevron.base, __styles.chevron.align[chevronAlign]]
-
- if (open && !disabled) {
- chevronClasses.unshift('!rotate-180')
- }
+ let { type, justified } = useContext(AccordionContext)
return (
- {
setOpen(!open)
}}
- >
-
- {header}
- {!disabled && (
-
- )}
-
-
- {children}
-
-
+ {...props}
+ />
)
-}
+})
+AccordionItem.displayName = AccordionPrimitive.Item.displayName
-Accordion.Item = Item
-export default Accordion
+export const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => {
+ const { type } = useContext(AccordionContext)
+ return (
+
+
+ {children}
+
+
+
+ )
+})
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+export const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => {
+ const { type } = useContext(AccordionContext)
+ return (
+
+ {children}
+
+ )
+})
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+Accordion.Item = AccordionItem
+Accordion.Content = AccordionContent
+Accordion.Trigger = AccordionTrigger
+export { Accordion }