mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 21:59:16 +03:00
Accordion updated
This commit is contained in:
1 parent
980ca5d536
commit
34794e3565
5 files changed
+584
-309
No files matched your search
@@ -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',
|
||||
|
||||
@@ -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) => (
|
||||
<Accordion {...args} justified={false}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
export const MultipleItems = (args: AccordionProps) => (
|
||||
<Accordion {...args} openBehaviour="multiple">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
export const Bordered = (args: AccordionProps) => (
|
||||
<Accordion {...args}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
Bordered.args = {
|
||||
type: 'bordered',
|
||||
}
|
||||
|
||||
export const LeftAlignedChevron = (args: AccordionProps) => (
|
||||
<Accordion {...args} justified={false} chevronAlign="left">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">First item</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Second item</span>
|
||||
</>
|
||||
}
|
||||
id="second"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
// 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: <IconArrowUp />,
|
||||
// }
|
||||
@@ -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<ContextValue>({
|
||||
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 (
|
||||
<>
|
||||
<RadixAccordion.Root
|
||||
type={openBehaviour}
|
||||
onValueChange={handleOnChange}
|
||||
defaultValue={defaultValue}
|
||||
className={containerClasses.join(' ')}
|
||||
children={
|
||||
<AccordionContext.Provider value={{ ...contextValue }}>
|
||||
<div>{children}</div>
|
||||
</AccordionContext.Provider>
|
||||
}
|
||||
></RadixAccordion.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<RadixAccordion.Item
|
||||
value={id}
|
||||
className={__styles.variants[type].container}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
setOpen(!open)
|
||||
}}
|
||||
>
|
||||
<RadixAccordion.Trigger className={triggerClasses.join(' ')}>
|
||||
{header}
|
||||
{!disabled && (
|
||||
<IconChevronDown aria-hidden className={chevronClasses.join(' ')} strokeWidth={2} />
|
||||
)}
|
||||
</RadixAccordion.Trigger>
|
||||
<RadixAccordion.Content className={__styles.variants[type].content}>
|
||||
<div className={__styles.variants[type].panel}>{children}</div>
|
||||
</RadixAccordion.Content>
|
||||
</RadixAccordion.Item>
|
||||
)
|
||||
}
|
||||
|
||||
Accordion.Item = Item
|
||||
export default Accordion
|
||||
@@ -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<typeof Accordion> = {
|
||||
/* 👇 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) => (
|
||||
<Accordion {...args} justified={false}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
export default meta
|
||||
|
||||
export const MultipleItems = (args: AccordionProps) => (
|
||||
<Accordion {...args} openBehaviour="multiple">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
type Story = StoryObj<typeof Accordion>
|
||||
|
||||
export const Bordered = (args: AccordionProps) => (
|
||||
<Accordion {...args}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
/*
|
||||
*👇 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) => (
|
||||
<Accordion type={type} openBehaviour="single" collapsible className="w-full">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">Title of the thing</span>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>Is it styled?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It comes with default styles that matches the other components' aesthetic.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Is it animated?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It's animated by default, but you can disable it if you prefer.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
),
|
||||
}
|
||||
|
||||
export const LeftAlignedChevron = (args: AccordionProps) => (
|
||||
<Accordion {...args} justified={false} chevronAlign="left">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
First item
|
||||
</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Second item
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
id="second"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
// 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: <IconArrowUp />,
|
||||
// }
|
||||
export const Bordered = {
|
||||
args: {
|
||||
type: 'bordered',
|
||||
},
|
||||
render: ({ children, type }: any) => (
|
||||
<Accordion type={type} openBehaviour="single" collapsible className="w-full">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>Is it accessible?</Accordion.Trigger>
|
||||
<Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>Is it styled?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It comes with default styles that matches the other components' aesthetic.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Is it animated?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Yes. It's animated by default, but you can disable it if you prefer.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
),
|
||||
}
|
||||
@@ -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<ContextValue>({
|
||||
chevronAlign: 'left',
|
||||
justified: true,
|
||||
type: 'default',
|
||||
export type AccordionRootVariantsProps = VariantProps<typeof accordionRootVariants>
|
||||
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<typeof accordionItemVariants>
|
||||
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 */}
|
||||
<RadixAccordion.Root
|
||||
type={openBehaviour}
|
||||
onValueChange={handleOnChange}
|
||||
defaultValue={defaultValue}
|
||||
className={containerClasses.join(' ')}
|
||||
children={
|
||||
<AccordionContext.Provider value={{ ...contextValue }}>
|
||||
<div>{children}</div>
|
||||
</AccordionContext.Provider>
|
||||
}
|
||||
></RadixAccordion.Root>
|
||||
</>
|
||||
)
|
||||
interface AccordionComponent
|
||||
extends React.ForwardRefExoticComponent<
|
||||
ExtendedAccordionProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
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<ContextValue>({
|
||||
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 (
|
||||
<AccordionPrimitive.Root
|
||||
ref={ref}
|
||||
type="single"
|
||||
onValueChange={handleOnChange}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
className={cn(accordionRootVariants({ type }), className)}
|
||||
children={
|
||||
<AccordionContext.Provider value={{ ...contextValue }}>
|
||||
{children}
|
||||
</AccordionContext.Provider>
|
||||
}
|
||||
{...singleProps}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
const { value, children, className, ...multipleProps } = props as AccordionMultipleProps
|
||||
return (
|
||||
<AccordionPrimitive.Root
|
||||
ref={ref}
|
||||
type="multiple"
|
||||
onValueChange={handleOnChange}
|
||||
value={value}
|
||||
className={cn(accordionRootVariants({ type }), className)}
|
||||
children={
|
||||
<AccordionContext.Provider value={{ ...contextValue }}>
|
||||
<div>{props.children}</div>
|
||||
</AccordionContext.Provider>
|
||||
}
|
||||
{...multipleProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
) as AccordionComponent
|
||||
|
||||
export interface ItemAdditionalProps
|
||||
extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> {}
|
||||
|
||||
export const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ 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 (
|
||||
<RadixAccordion.Item
|
||||
value={id}
|
||||
className={__styles.variants[type].container}
|
||||
disabled={disabled}
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(accordionItemVariants({ type, justified }), className)}
|
||||
onClick={() => {
|
||||
setOpen(!open)
|
||||
}}
|
||||
>
|
||||
<RadixAccordion.Trigger className={triggerClasses.join(' ')}>
|
||||
{header}
|
||||
{!disabled && (
|
||||
<IconChevronDown aria-hidden className={chevronClasses.join(' ')} strokeWidth={2} />
|
||||
)}
|
||||
</RadixAccordion.Trigger>
|
||||
<RadixAccordion.Content className={__styles.variants[type].content}>
|
||||
<div className={__styles.variants[type].panel}>{children}</div>
|
||||
</RadixAccordion.Content>
|
||||
</RadixAccordion.Item>
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
AccordionItem.displayName = AccordionPrimitive.Item.displayName
|
||||
|
||||
Accordion.Item = Item
|
||||
export default Accordion
|
||||
export const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { type } = useContext(AccordionContext)
|
||||
return (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(accordionTriggerVariants({ type }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
)
|
||||
})
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
export const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { type } = useContext(AccordionContext)
|
||||
return (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cx(accordionContentVariants({ type }))}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
)
|
||||
})
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
Accordion.Item = AccordionItem
|
||||
Accordion.Content = AccordionContent
|
||||
Accordion.Trigger = AccordionTrigger
|
||||
export { Accordion }
|
||||
Reference in new issue
Block a user