diff --git a/deepfence_frontend/packages/ui-components/package.json b/deepfence_frontend/packages/ui-components/package.json index 2668f8977c..745f1b1b74 100644 --- a/deepfence_frontend/packages/ui-components/package.json +++ b/deepfence_frontend/packages/ui-components/package.json @@ -25,6 +25,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { + "@radix-ui/react-accordion": "^1.0.1", "@radix-ui/react-checkbox": "^1.0.0", "@radix-ui/react-dialog": "^1.0.0", "@radix-ui/react-dropdown-menu": "^1.0.0", diff --git a/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.stories.tsx b/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.stories.tsx new file mode 100644 index 0000000000..d5b307bf1d --- /dev/null +++ b/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.stories.tsx @@ -0,0 +1,43 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from './Accordion'; + +export default { + title: 'Components/Accordion', + component: Accordion, +} as ComponentMeta; + +const Items = [ + `Lorem Ipsum is simply dummy text of the printing and typesetting industry.`, + 'Item 2', + 'Item 3', +]; + +const DefaultTemplate: ComponentStory = (args) => { + return ( + + {Items.map((item: string) => { + return ( + + Click {item} + {`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, + and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`} + + ); + })} + + ); +}; + +export const Default = DefaultTemplate.bind({}); +Default.args = { + type: 'multiple', + defaultValue: ['Item 2'], +}; diff --git a/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.tsx b/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.tsx new file mode 100644 index 0000000000..e3c765f69a --- /dev/null +++ b/deepfence_frontend/packages/ui-components/src/components/accordion/Accordion.tsx @@ -0,0 +1,97 @@ +import * as AccordionPrimitive from '@radix-ui/react-accordion'; +import cx from 'classnames'; +import React from 'react'; +import { HiOutlineChevronDown } from 'react-icons/hi'; +import { twMerge } from 'tailwind-merge'; + +import { Typography } from '../typography/Typography'; + +export const Accordion = React.forwardRef< + HTMLDivElement, + AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps +>(({ children, className, ...rest }, forwardedRef) => ( + + {children} + +)); + +export interface AccordionItemProps extends AccordionPrimitive.AccordionItemProps { + children: React.ReactNode; + className?: string; +} + +export const AccordionItem = (props: AccordionItemProps) => { + const { children, value = '', className = '', ...rest } = props; + return ( + + {children} + + ); +}; + +export const AccordionTrigger = React.forwardRef< + HTMLButtonElement, + AccordionPrimitive.AccordionTriggerProps +>(({ children, className, ...props }, forwardedRef) => ( + + + {children} + + + + + +)); + +export const AccordionContent = React.forwardRef< + HTMLDivElement, + AccordionPrimitive.AccordionContentProps +>(({ children, className, ...props }, forwardedRef) => ( + +
{children}
+
+)); diff --git a/deepfence_frontend/packages/ui-components/tailwind.config.cjs b/deepfence_frontend/packages/ui-components/tailwind.config.cjs index a898e6de70..6d4addefed 100644 --- a/deepfence_frontend/packages/ui-components/tailwind.config.cjs +++ b/deepfence_frontend/packages/ui-components/tailwind.config.cjs @@ -239,6 +239,22 @@ module.exports = { '100%': { opacity: 1, transform: 'scale(1)' }, '0%': { opacity: 0, transform: 'scale(0.96)' }, }, + 'accordion-slide-down': { + from: { + height: 0, + }, + to: { + height: 'var(--radix-accordion-content-height)', + }, + }, + 'accordion-slide-up': { + from: { + height: 'var(--radix-accordion-content-height)', + }, + to: { + height: 0, + }, + }, }, animation: { // tooltip @@ -258,6 +274,8 @@ module.exports = { 'slide-opacity-out': 'slide-opacity-out 0.3s ease', 'opacity-out': 'opacity-out 0.5s ease', 'opacity-in': 'opacity-in 0.5s ease', + 'accordion-open': 'accordion-slide-down 100ms cubic-bezier(0.16, 1, 0.3, 1)', + 'accordion-closed': 'accordion-slide-up 100ms cubic-bezier(0.16, 1, 0.3, 1)', }, }, }, diff --git a/deepfence_frontend/pnpm-lock.yaml b/deepfence_frontend/pnpm-lock.yaml index 20f68e16bc..633d1ee8be 100644 --- a/deepfence_frontend/pnpm-lock.yaml +++ b/deepfence_frontend/pnpm-lock.yaml @@ -80,6 +80,7 @@ importers: packages/ui-components: specifiers: '@babel/core': ^7.19.1 + '@radix-ui/react-accordion': ^1.0.1 '@radix-ui/react-checkbox': ^1.0.0 '@radix-ui/react-dialog': ^1.0.0 '@radix-ui/react-dropdown-menu': ^1.0.0 @@ -140,6 +141,7 @@ importers: vite-plugin-dts: ^1.7.1 vitest: ^0.23.4 dependencies: + '@radix-ui/react-accordion': 1.0.1_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-checkbox': 1.0.1_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-dialog': 1.0.2_2zx2umvpluuhvlq44va5bta2da '@radix-ui/react-dropdown-menu': 1.0.0_2zx2umvpluuhvlq44va5bta2da @@ -2227,6 +2229,25 @@ packages: '@babel/runtime': 7.20.6 dev: false + /@radix-ui/react-accordion/1.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-Ka7BQoyRRPwsOb0YEv0fQU8V8aCXCxAzNVI5BRN7WmPG2E1zQKKxmb86NVDqIj6uSnaahJ1E5S6bX5Lk9hTK+g==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.20.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-collapsible': 1.0.1_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-id': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@radix-ui/react-arrow/1.0.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==} peerDependencies: @@ -2270,6 +2291,25 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /@radix-ui/react-collapsible/1.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-0maX4q91iYa4gjt3PsNf7dq/yqSR+HGAE8I5p54dQ6gnveS+ETWlMoijxrhmgV1k8svxpm34mQAtqIrJt4XZmA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.20.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-id': 1.0.0_react@18.2.0 + '@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /@radix-ui/react-collection/1.0.0_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==} peerDependencies: