From 21f24463ef8449c782816d00ef8f92fab3adc39d Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Fri, 6 Dec 2024 14:10:40 -0500 Subject: [PATCH 1/2] feat(ui) Add alchemy component library to FE --- .../alchemy-components/.docs/Contributing.mdx | 43 ++ .../alchemy-components/.docs/DesignTokens.mdx | 63 ++ .../src/alchemy-components/.docs/Icons.mdx | 34 ++ .../src/alchemy-components/.docs/Intro.mdx | 14 + .../alchemy-components/.docs/StyleGuide.mdx | 209 +++++++ .../.docs/mdx-components/CodeBlock.tsx | 24 + .../.docs/mdx-components/CopyButton.tsx | 16 + .../.docs/mdx-components/GridList.tsx | 32 + .../mdx-components/IconGalleryWithSearch.tsx | 291 ++++++++++ .../.docs/mdx-components/components.ts | 110 ++++ .../.docs/mdx-components/index.ts | 6 + .../.docs/mdx-components/utils.ts | 15 + .../src/alchemy-components/README.mdx | 73 +++ .../components/Avatar/Avatar.stories.tsx | 133 +++++ .../components/Avatar/Avatar.tsx | 40 ++ .../Avatar/_tests_/getNameInitials.test.ts | 34 ++ .../components/Avatar/components.ts | 51 ++ .../components/Avatar/index.ts | 1 + .../components/Avatar/types.ts | 10 + .../components/Avatar/utils.ts | 64 ++ .../components/Badge/Badge.stories.tsx | 102 ++++ .../components/Badge/Badge.tsx | 29 + .../components/Badge/components.ts | 6 + .../components/Badge/index.ts | 1 + .../components/Badge/types.ts | 8 + .../components/Badge/utils.ts | 15 + .../components/BarChart/BarChart.stories.tsx | 90 +++ .../components/BarChart/BarChart.tsx | 152 +++++ .../components/BarChart/components.tsx | 34 ++ .../components/BarChart/index.ts | 1 + .../components/BarChart/types.ts | 18 + .../components/BarChart/utils.ts | 26 + .../components/Button/Button.stories.tsx | 203 +++++++ .../components/Button/Button.tsx | 60 ++ .../components/Button/components.ts | 27 + .../components/Button/index.ts | 2 + .../components/Button/types.ts | 16 + .../components/Button/utils.ts | 238 ++++++++ .../components/Card/Card.stories.tsx | 141 +++++ .../components/Card/Card.tsx | 48 ++ .../components/Card/components.ts | 59 ++ .../components/Card/index.ts | 2 + .../components/Card/types.ts | 13 + .../components/Checkbox/Checkbox.stories.tsx | 156 +++++ .../components/Checkbox/Checkbox.tsx | 103 ++++ .../components/Checkbox/components.ts | 91 +++ .../components/Checkbox/index.ts | 2 + .../components/Checkbox/types.ts | 16 + .../components/Checkbox/utils.ts | 27 + .../components/Heading/Heading.stories.tsx | 98 ++++ .../components/Heading/Heading.tsx | 38 ++ .../components/Heading/components.ts | 70 +++ .../components/Heading/index.ts | 2 + .../components/Heading/types.ts | 9 + .../components/Icon/Icon.stories.tsx | 131 +++++ .../components/Icon/Icon.tsx | 59 ++ .../components/Icon/components.ts | 19 + .../components/Icon/constants.ts | 547 ++++++++++++++++++ .../components/Icon/index.ts | 3 + .../components/Icon/types.ts | 23 + .../components/Icon/utils.ts | 29 + .../components/Input/Input.stories.tsx | 177 ++++++ .../components/Input/Input.tsx | 97 ++++ .../components/Input/components.ts | 92 +++ .../components/Input/index.ts | 2 + .../components/Input/types.ts | 22 + .../components/Input/utils.ts | 5 + .../LineChart/LineChart.stories.tsx | 96 +++ .../components/LineChart/LineChart.tsx | 177 ++++++ .../components/LineChart/components.tsx | 8 + .../components/LineChart/index.ts | 1 + .../components/LineChart/types.ts | 22 + .../PageTitle/PageTitle.stories.tsx | 71 +++ .../components/PageTitle/PageTitle.tsx | 17 + .../components/PageTitle/components.ts | 52 ++ .../components/PageTitle/index.ts | 1 + .../components/PageTitle/types.ts | 8 + .../components/PageTitle/utils.ts | 27 + .../components/Pills/Pill.stories.tsx | 126 ++++ .../components/Pills/Pill.tsx | 42 ++ .../components/Pills/components.ts | 33 ++ .../components/Pills/index.ts | 1 + .../components/Pills/types.ts | 18 + .../components/Pills/utils.ts | 147 +++++ .../components/Popover/Popover.tsx | 6 + .../components/Popover/index.ts | 1 + .../components/Radio/Radio.stories.tsx | 136 +++++ .../components/Radio/Radio.tsx | 89 +++ .../components/Radio/components.ts | 83 +++ .../components/Radio/types.ts | 16 + .../components/Radio/utils.ts | 27 + .../components/Select/BasicSelect.tsx | 339 +++++++++++ .../components/Select/Nested/NestedOption.tsx | 309 ++++++++++ .../components/Select/Nested/NestedSelect.tsx | 312 ++++++++++ .../components/Select/Nested/types.ts | 9 + .../components/Select/Select.stories.tsx | 431 ++++++++++++++ .../components/Select/Select.tsx | 65 +++ .../components/Select/SimpleSelect.tsx | 299 ++++++++++ .../components/Select/components.ts | 235 ++++++++ .../components/Select/index.ts | 3 + .../components/Select/types.ts | 61 ++ .../components/Select/utils.ts | 125 ++++ .../components/Switch/Switch.stories.tsx | 169 ++++++ .../components/Switch/Switch.tsx | 74 +++ .../components/Switch/components.ts | 118 ++++ .../components/Switch/index.ts | 2 + .../components/Switch/types.ts | 21 + .../components/Switch/utils.ts | 97 ++++ .../components/Table/Table.stories.tsx | 162 ++++++ .../components/Table/Table.tsx | 115 ++++ .../components/Table/components.ts | 94 +++ .../components/Table/index.ts | 2 + .../components/Table/types.ts | 21 + .../components/Table/utils.ts | 73 +++ .../components/Text/Text.stories.tsx | 100 ++++ .../components/Text/Text.tsx | 33 ++ .../components/Text/components.ts | 50 ++ .../components/Text/index.ts | 2 + .../components/Text/types.ts | 9 + .../components/TextArea/TextArea.stories.tsx | 159 +++++ .../components/TextArea/TextArea.tsx | 80 +++ .../components/TextArea/components.ts | 106 ++++ .../components/TextArea/index.ts | 2 + .../components/TextArea/types.ts | 15 + .../components/Tooltip/Tooltip.tsx | 6 + .../components/Tooltip/index.ts | 1 + .../components/commonStyles.ts | 23 + .../src/alchemy-components/index.ts | 23 + .../theme/config/constants.ts | 1 + .../alchemy-components/theme/config/index.ts | 2 + .../alchemy-components/theme/config/types.ts | 47 ++ .../theme/foundations/blur.ts | 12 + .../theme/foundations/borders.ts | 9 + .../theme/foundations/breakpoints.ts | 10 + .../theme/foundations/colors.ts | 98 ++++ .../theme/foundations/index.ts | 27 + .../theme/foundations/radius.ts | 9 + .../theme/foundations/shadows.ts | 16 + .../theme/foundations/sizes.ts | 7 + .../theme/foundations/spacing.ts | 12 + .../theme/foundations/transform.ts | 10 + .../theme/foundations/transition.ts | 32 + .../theme/foundations/typography.ts | 52 ++ .../theme/foundations/zIndex.ts | 17 + .../src/alchemy-components/theme/index.ts | 30 + .../theme/semantic-tokens.ts | 21 + .../src/alchemy-components/theme/utils.ts | 62 ++ datahub-web-react/tsconfig.json | 30 +- datahub-web-react/vite.config.ts | 28 + 149 files changed, 9851 insertions(+), 3 deletions(-) create mode 100644 datahub-web-react/src/alchemy-components/.docs/Contributing.mdx create mode 100644 datahub-web-react/src/alchemy-components/.docs/DesignTokens.mdx create mode 100644 datahub-web-react/src/alchemy-components/.docs/Icons.mdx create mode 100644 datahub-web-react/src/alchemy-components/.docs/Intro.mdx create mode 100644 datahub-web-react/src/alchemy-components/.docs/StyleGuide.mdx create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/CodeBlock.tsx create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/CopyButton.tsx create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/GridList.tsx create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/IconGalleryWithSearch.tsx create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/components.ts create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/index.ts create mode 100644 datahub-web-react/src/alchemy-components/.docs/mdx-components/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/README.mdx create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/Avatar.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/Avatar.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/_tests_/getNameInitials.test.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Avatar/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/Badge.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/Badge.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Badge/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/BarChart.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/BarChart.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/components.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/BarChart/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Button/Button.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Button/Button.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Button/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Button/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Button/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Button/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Card/Card.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Card/Card.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Card/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Card/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Card/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/Checkbox.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/Checkbox.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Checkbox/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Heading/Heading.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Heading/Heading.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Heading/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Heading/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Heading/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/Icon.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/Icon.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/constants.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Icon/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Input/Input.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Input/Input.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Input/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Input/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Input/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Input/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/LineChart/LineChart.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/LineChart/LineChart.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/LineChart/components.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/LineChart/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/LineChart/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/PageTitle.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/PageTitle.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/PageTitle/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/Pill.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/Pill.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Pills/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Popover/Popover.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Popover/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Radio/Radio.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Radio/Radio.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Radio/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Radio/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Radio/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Select/BasicSelect.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/Nested/NestedOption.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/Nested/NestedSelect.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/Nested/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Select/Select.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/Select.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/SimpleSelect.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Select/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Select/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Select/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Select/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/Switch.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/Switch.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Switch/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Table/Table.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Table/Table.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Table/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Table/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Table/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Table/utils.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Text/Text.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Text/Text.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Text/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Text/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Text/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/TextArea/TextArea.stories.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/TextArea/TextArea.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/TextArea/components.ts create mode 100644 datahub-web-react/src/alchemy-components/components/TextArea/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/TextArea/types.ts create mode 100644 datahub-web-react/src/alchemy-components/components/Tooltip/Tooltip.tsx create mode 100644 datahub-web-react/src/alchemy-components/components/Tooltip/index.ts create mode 100644 datahub-web-react/src/alchemy-components/components/commonStyles.ts create mode 100644 datahub-web-react/src/alchemy-components/index.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/config/constants.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/config/index.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/config/types.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/blur.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/borders.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/breakpoints.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/colors.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/index.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/radius.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/shadows.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/sizes.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/spacing.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/transform.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/transition.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/typography.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/foundations/zIndex.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/index.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/semantic-tokens.ts create mode 100644 datahub-web-react/src/alchemy-components/theme/utils.ts diff --git a/datahub-web-react/src/alchemy-components/.docs/Contributing.mdx b/datahub-web-react/src/alchemy-components/.docs/Contributing.mdx new file mode 100644 index 0000000000000..75a31d011903f --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/Contributing.mdx @@ -0,0 +1,43 @@ +import { Meta } from '@storybook/blocks'; + + + +
+ ## Contributing + + Building and maintinging a design system is a collaborative effort. We welcome contributions from all team members, regardless of their role or experience level. This document outlines the process for contributing to the Acryl Component Library. + + ### Development + + To run Storybook locally, use the following command: + + ``` + yarn storybook + ``` + + Storybook will start a local development server and open a new browser window with the Storybook interface on port `6006`. When developing new components or updating existing ones, you can use Storybook to preview your changes in real-time. This will ensure that the component looks and behaves as expected before merging your changes. + + ### Crafting New Components + + When creating new components, make sure to follow the established design patterns and coding standards. This will help maintain consistency across all Acryl products and make it easier for other team members to understand and use your components. + + Design new components with reusability in mind. Components should be flexible, extensible, and easy to customize. Avoid hardcoding values and use props to pass data and styles to your components. This will make it easier to reuse the component in different contexts and scenarios. + + Our design team works exclusively in Figma, so if questions arise about the design or implementation of a component, please refer to the Figma files for more information. If you have any questions or need clarification, feel free to reach out to the design team for assistance. + + ### Pull Requests + + When submitting a pull request, please follow these guidelines: + + 1. Create a new branch for your changes. + 2. Make sure your code is well-documented and follows the established coding standards. + 3. Write clear and concise commit messages. + 4. Include a detailed description of the changes in your pull request. + + If applicable, include screenshots or GIFs to demonstrate the changes visually. This will help reviewers understand the context of your changes and provide more accurate feedback. If a Figma file exists, include a link to the file in the pull request description. + + ### Review Process + + All pull requests will be reviewed by the UI and design team to ensure that the changes align with the design system guidelines and best practices. The team will provide feedback and suggestions for improvement, and you may be asked to make additional changes before your pull request is merged. + +
diff --git a/datahub-web-react/src/alchemy-components/.docs/DesignTokens.mdx b/datahub-web-react/src/alchemy-components/.docs/DesignTokens.mdx new file mode 100644 index 0000000000000..0ebdebbf9db4c --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/DesignTokens.mdx @@ -0,0 +1,63 @@ +import { Meta, Source } from '@storybook/blocks'; + +import theme from '@components/theme'; + +import { ColorCard, CopyButton } from './mdx-components'; + + + +
+ ## Design Tokens + + To streamline the design process and ensure consistency across all Acryl products, we use a set of design tokens that define the visual properties of our design system. These tokens include colors, typography, spacing, and other visual elements that can be used to create a cohesive user experience. + + ### Colors + + ```tsx + import theme from '@components/theme'; + + // Accessing a color via object path +
Hello, World!
+ + // Using CSS variables +
Hello, World!
+ ``` + + + + + + + + + + + {Object.keys(theme.semanticTokens.colors).map((color) => { + const objectKey = `colors['${color}']`; + const hexValue = theme.semanticTokens.colors[color]; + const cssVar = `--alch-color-${color}`; + + return ( + + + + + + ); + })} + +
Token ValueSelectorCSS Variable (coming soon)
+ + +
+ {color} + {hexValue} +
+
+
+ + {objectKey} + + {cssVar}
+ +
diff --git a/datahub-web-react/src/alchemy-components/.docs/Icons.mdx b/datahub-web-react/src/alchemy-components/.docs/Icons.mdx new file mode 100644 index 0000000000000..e3f6ab6846119 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/Icons.mdx @@ -0,0 +1,34 @@ +import { Meta, Source } from '@storybook/blocks'; + +import { AVAILABLE_ICONS } from '@components'; +import { IconGalleryWithSearch } from './mdx-components'; + + + +
+ ## Icons + + Under the hood, we're utilizing the Material Design Icon Library. However, we've crafted out own resuable component to make it easier to use these icons in our application. + + + View the component documentation to learn more + + + In addition to using Materials Design Icons, we've also added a few custom icons to the library. You can access them through the same `` component and are represented in the list of available options below. + + ```tsx + import { Icon } from '@components'; + + + ``` + +
+ + ### Gallery + + There are {AVAILABLE_ICONS.length} icons available.
+ Name values populate the `icon` prop on the `` component. + + + +
diff --git a/datahub-web-react/src/alchemy-components/.docs/Intro.mdx b/datahub-web-react/src/alchemy-components/.docs/Intro.mdx new file mode 100644 index 0000000000000..f81d08059c7b4 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/Intro.mdx @@ -0,0 +1,14 @@ +import { Meta, Description } from '@storybook/blocks'; +import ReadMe from '../README.mdx'; + + + +
+
+ Acryl Logo +
+ + {/* To simply, we're rendering the root readme here */} + + +
diff --git a/datahub-web-react/src/alchemy-components/.docs/StyleGuide.mdx b/datahub-web-react/src/alchemy-components/.docs/StyleGuide.mdx new file mode 100644 index 0000000000000..43199cbbca62d --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/StyleGuide.mdx @@ -0,0 +1,209 @@ +import { Meta, Source } from '@storybook/blocks'; + +import { Heading } from '@components'; +import { colors } from '@components/theme'; + +import { Grid, FlexGrid, ColorCard, CopyButton, Seperator } from './mdx-components'; + +import borderSource from '@components/theme/foundations/borders?raw'; +import colorsSource from '@components/theme/foundations/colors?raw'; +import typographySource from '@components/theme/foundations/typography?raw'; +import radiusSource from '@components/theme/foundations/radius?raw'; +import shadowsSource from '@components/theme/foundations/shadows?raw'; +import sizesSource from '@components/theme/foundations/sizes?raw'; +import spacingSource from '@components/theme/foundations/spacing?raw'; +import transitionSource from '@components/theme/foundations/transition?raw'; +import zIndexSource from '@components/theme/foundations/zIndex?raw'; + + + +
+ ## Style Guide + + The purpose of this Style Guide is to establish a unified and cohesive design language that ensures a consistent user experience across all Acryl products. By adhering to these guidelines, we can maintain a high standard of design quality and improve the usability of our applications. + + ### Theme + + You can import the theme object into any component or file in your application and use it to style your components. The theme object is a single source of truth for your application's design system. + + ```tsx + import { typography, colors, spacing } from '@components/theme'; + ``` + + ### Colors + + Colors are managed via the `colors.ts` file in the `theme/foundations` directory. The colors are defined as a nested object with the following structure: + + + + By default, all `500` values are considered the "default" value of that color range. For example, `gray.500` is the default gray color. The other values are used for shading and highlighting. Color values are defined in hex format and their values range between 25 and 1000. With 25 being the lighest and 1000 being the darkest. + + #### Black & White + + + +
+ Black + {colors['black']} +
+
+ + +
+ White + {colors['white']} +
+
+
+ + + + #### Gray + + {Object.keys(colors.gray).map((color) => ( + + +
+ + Gray {color} + + {colors['gray'][color]} +
+
+ ))} +
+ + + + #### Violet (Primary) + + {Object.keys(colors.violet).map((color) => ( + + +
+ + Violet {color} + + {colors['violet'][color]} +
+
+ ))} +
+ + + + #### Blue + + {Object.keys(colors.blue).map((color) => ( + + +
+ + Blue {color} + + {colors['blue'][color]} +
+
+ ))} +
+ + + + #### Green + + {Object.keys(colors.green).map((color) => ( + + +
+ + Green {color} + + {colors['green'][color]} +
+
+ ))} +
+ + + + #### Yellow + + {Object.keys(colors.yellow).map((color) => ( + + +
+ + Yellow {color} + + {colors['yellow'][color]} +
+
+ ))} +
+ + + + #### Red + + {Object.keys(colors.red).map((color) => ( + + +
+ + Red {color} + + {colors['red'][color]} +
+
+ ))} +
+ + ### Typography + + Font styles are managed via the `typography.ts` file in the `theme/foundations` directory. The primary font family in use is `Mulish`. The font styles are defined as a nested object with the following structure: + + + + ### Borders + + A set of border values defined by the border key. + + + + ### Border Radius + + A set smooth corner radius values defined by the radii key. + + + + ### Shadows + + A set of shadow values defined by the shadows key. + + + + ## Sizes + + A set of size values defined by the sizes key. + + + + ### Spacing + + A set of spacing values defined by the spacing key. + + + + ### Transitions + + A set of transition values defined by the transition key. + + + + ### Z-Index + + A set of z-index values defined by the zindex key. + + + +
diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/CodeBlock.tsx b/datahub-web-react/src/alchemy-components/.docs/mdx-components/CodeBlock.tsx new file mode 100644 index 0000000000000..43b9ebfae6414 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/CodeBlock.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { Source, DocsContext } from '@storybook/blocks'; + +export const CodeBlock = () => { + const context = React.useContext(DocsContext); + + const { primaryStory } = context as any; + const component = context ? primaryStory.component.__docgenInfo.displayName : ''; + + if (!context || !primaryStory) return null; + + return ( +
+ +
+ ); +}; diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/CopyButton.tsx b/datahub-web-react/src/alchemy-components/.docs/mdx-components/CopyButton.tsx new file mode 100644 index 0000000000000..c81aa6ed44289 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/CopyButton.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import { Button, Icon } from '@components'; +import { copyToClipboard } from './utils'; + +interface Props { + text: string; +} + +export const CopyButton = ({ text }: Props) => ( +
+ +
+); diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/GridList.tsx b/datahub-web-react/src/alchemy-components/.docs/mdx-components/GridList.tsx new file mode 100644 index 0000000000000..5cb4bd27e521a --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/GridList.tsx @@ -0,0 +1,32 @@ +/* + Docs Only Component that helps to display a list of components in a grid layout. +*/ + +import React, { ReactNode } from 'react'; + +const styles = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '8px', +}; + +interface Props { + isVertical?: boolean; + width?: number | string; + children: ReactNode; +} + +export const GridList = ({ isVertical = false, width = '100%', children }: Props) => { + return ( +
+ {children} +
+ ); +}; diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/IconGalleryWithSearch.tsx b/datahub-web-react/src/alchemy-components/.docs/mdx-components/IconGalleryWithSearch.tsx new file mode 100644 index 0000000000000..d8751509bd6a7 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/IconGalleryWithSearch.tsx @@ -0,0 +1,291 @@ +import React, { useState } from 'react'; + +import { Icon, Button, ButtonProps } from '@components'; +import { IconGrid, IconGridItem, IconDisplayBlock } from './components'; + +interface Props { + icons: string[]; +} + +export const IconGalleryWithSearch = ({ icons }: Props) => { + const [iconSet, setIconSet] = useState(icons); + const [search, setSearch] = useState(''); + const [variant, setVariant] = useState('outline'); + + const filteredIcons = iconSet.filter((icon) => icon.toLowerCase().includes(search.toLowerCase())); + + const arrows = [ + 'ArrowBack', + 'ArrowCircleDown', + 'ArrowCircleLeft', + 'ArrowCircleRight', + 'ArrowCircleUp', + 'ArrowDownward', + 'ArrowForward', + 'ArrowOutward', + 'ArrowUpward', + 'CloseFullscreen', + 'Cached', + 'Code', + 'CodeOff', + 'CompareArrows', + 'Compress', + 'ChevronLeft', + 'ChevronRight', + 'DoubleArrow', + 'FastForward', + 'FastRewind', + 'FileDownload', + 'FileUpload', + 'ForkLeft', + 'ForkRight', + 'GetApp', + 'LastPage', + 'Launch', + 'Login', + 'Logout', + 'LowPriority', + 'ManageHistory', + 'Merge', + 'MergeType', + 'MoveUp', + 'MultipleStop', + 'OpenInFull', + 'Outbound', + 'Outbox', + 'Output', + 'PlayArrow', + 'PlayCircle', + 'Publish', + 'ReadMore', + 'ExitToApp', + 'Redo', + 'Refresh', + 'Replay', + 'ReplyAll', + 'Reply', + 'Restore', + 'SaveAlt', + 'Shortcut', + 'SkipNext', + 'SkipPrevious', + 'Start', + 'Straight', + 'SubdirectoryArrowLeft', + 'SubdirectoryArrowRight', + 'SwapHoriz', + 'SwapVert', + 'SwitchLeft', + 'SwitchRight', + 'SyncAlt', + 'SyncDisabled', + 'SyncLock', + 'Sync', + 'Shuffle', + 'SyncProblem', + 'TrendingDown', + 'TrendingFlat', + 'TrendingUp', + 'TurnLeft', + 'TurnRight', + 'TurnSlightLeft', + 'TurnSlightRight', + 'Undo', + 'UnfoldLessDouble', + 'UnfoldLess', + 'UnfoldMoreDouble', + 'UnfoldMore', + 'UpdateDisabled', + 'Update', + 'Upgrade', + 'Upload', + 'ZoomInMap', + 'ZoomOutMap', + ]; + + const dataViz = [ + 'AccountTree', + 'Analytics', + 'ArtTrack', + 'Article', + 'BackupTable', + 'BarChart', + 'BubbleChart', + 'Calculate', + 'Equalizer', + 'List', + 'FormatListBulleted', + 'FormatListNumbered', + 'Grading', + 'InsertChart', + 'Hub', + 'Insights', + 'Lan', + 'Leaderboard', + 'LegendToggle', + 'Map', + 'MultilineChart', + 'Nat', + 'PivotTableChart', + 'Poll', + 'Polyline', + 'QueryStats', + 'Radar', + 'Route', + 'Rule', + 'Schema', + 'Sort', + 'SortByAlpha', + 'ShowChart', + 'Source', + 'SsidChart', + 'StackedBarChart', + 'StackedLineChart', + 'Storage', + 'TableChart', + 'TableRows', + 'TableView', + 'Timeline', + 'ViewAgenda', + 'ViewArray', + 'ViewCarousel', + 'ViewColumn', + 'ViewComfy', + 'ViewCompact', + 'ViewCozy', + 'ViewDay', + 'ViewHeadline', + 'ViewKanban', + 'ViewList', + 'ViewModule', + 'ViewQuilt', + 'ViewSidebar', + 'ViewStream', + 'ViewTimeline', + 'ViewWeek', + 'Visibility', + 'VisibilityOff', + 'Webhook', + 'Window', + ]; + + const social = [ + 'AccountCircle', + 'Badge', + 'Campaign', + 'Celebration', + 'Chat', + 'ChatBubble', + 'CommentBank', + 'Comment', + 'CommentsDisabled', + 'Message', + 'ContactPage', + 'Contacts', + 'GroupAdd', + 'Group', + 'GroupRemove', + 'Groups', + 'Handshake', + 'ManageAccounts', + 'MoodBad', + 'SentimentDissatisfied', + 'SentimentNeutral', + 'SentimentSatisfied', + 'Mood', + 'NoAccounts', + 'People', + 'PersonAddAlt1', + 'PersonOff', + 'Person', + 'PersonRemoveAlt1', + 'PersonSearch', + 'SwitchAccount', + 'StarBorder', + 'StarHalf', + 'Star', + 'ThumbDown', + 'ThumbUp', + 'ThumbsUpDown', + 'Verified', + 'VerifiedUser', + ]; + + const notifs = [ + 'Mail', + 'Drafts', + 'MarkAsUnread', + 'Inbox', + 'Outbox', + 'MoveToInbox', + 'Unsubscribe', + 'Upcoming', + 'NotificationAdd', + 'NotificationImportant', + 'NotificationsActive', + 'NotificationsOff', + 'Notifications', + 'NotificationsPaused', + ]; + + const handleChangeSet = (set) => { + setIconSet(set); + setSearch(''); + }; + + const handleResetSet = () => { + setIconSet(icons); + setSearch(''); + }; + + const smButtonProps: ButtonProps = { + size: 'sm', + color: 'gray', + }; + + return ( + <> + setSearch(e.target.value)} + placeholder="Search for an icon…" + style={{ width: '100%', padding: '0.5rem', marginBottom: '0.5rem' }} + /> +
+
+ + + + + +
+
+ +
+
+ + {filteredIcons.map((icon) => ( + + + + + {icon} + + ))} + + + ); +}; diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/components.ts b/datahub-web-react/src/alchemy-components/.docs/mdx-components/components.ts new file mode 100644 index 0000000000000..28d428493b17b --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/components.ts @@ -0,0 +1,110 @@ +/* + Docs Only Components that helps to display information in info guides. +*/ + +import styled from 'styled-components'; + +import theme from '@components/theme'; + +export const Grid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +`; + +export const FlexGrid = styled.div` + display: flex; + gap: 16px; +`; + +export const VerticalFlexGrid = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const Seperator = styled.div` + height: 16px; +`; + +export const ColorCard = styled.div<{ color: string; size?: string }>` + display: flex; + gap: 16px; + align-items: center; + + ${({ size }) => + size === 'sm' && + ` + gap: 8px; + `} + + & span { + display: block; + line-height: 1.3; + } + + & .colorChip { + background: ${({ color }) => color}; + width: 3rem; + height: 3rem; + + ${({ size }) => + size === 'sm' && + ` + width: 2rem; + height: 2rem; + border-radius: 4px; + `} + + border-radius: 8px; + box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset; + } + + & .colorValue { + display: flex; + align-items: center; + gap: 0; + font-weight: bold; + font-size: 14px; + } + + & .hex { + font-size: 11px; + opacity: 0.5; + text-transform: uppercase; + } +`; + +export const IconGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 16px; + margin-top: 20px; +`; + +export const IconGridItem = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + + border: 1px solid ${theme.semanticTokens.colors['border-color']}; + border-radius: 8px; + overflow: hidden; + + & span { + width: 100%; + border-top: 1px solid ${theme.semanticTokens.colors['border-color']}; + background-color: ${theme.semanticTokens.colors['subtle-bg']}; + text-align: center; + padding: 4px 8px; + font-size: 10px; + } +`; + +export const IconDisplayBlock = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 50px; +`; diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/index.ts b/datahub-web-react/src/alchemy-components/.docs/mdx-components/index.ts new file mode 100644 index 0000000000000..d1c1848d1eb37 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/index.ts @@ -0,0 +1,6 @@ +export * from './CodeBlock'; +export * from './CopyButton'; +export * from './GridList'; +export * from './IconGalleryWithSearch'; +export * from './components'; +export * from './utils'; diff --git a/datahub-web-react/src/alchemy-components/.docs/mdx-components/utils.ts b/datahub-web-react/src/alchemy-components/.docs/mdx-components/utils.ts new file mode 100644 index 0000000000000..d4fa47dc9e967 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/.docs/mdx-components/utils.ts @@ -0,0 +1,15 @@ +/* + Docs related utils +*/ + +/** + * Copies the given text to the clipboard. + * @param {string} text - The text to be copied to the clipboard. + * @returns {Promise} A promise that resolves when the text is copied. + */ +export const copyToClipboard = (text: string) => { + return navigator.clipboard + .writeText(text) + .then(() => console.log(`${text} copied to clipboard`)) + .catch(); +}; diff --git a/datahub-web-react/src/alchemy-components/README.mdx b/datahub-web-react/src/alchemy-components/README.mdx new file mode 100644 index 0000000000000..5373432c0ede0 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/README.mdx @@ -0,0 +1,73 @@ +# Alchemy Component Library + +This is a comprehensive library of accessible and reusable React components that streamlines the development of Acryl's applications and websites. The library offers a diverse range of components that can be easily combined to build complex user interfaces while adhering to accessibility best practices. + +### Component Usage + +It's easy to use the components availble in the library. Simply import the component and use it anywhere you're rendering React components. + +```tsx +import { Button } from '@components'; + +function YourComponent() { + return ; +} +``` + +In addition to the components themselves, you can also import their types: + +```tsx +import type { ButtonProps } from '@components'; +``` + +### Theme Usage + +This component library comes with a complete theme utility that pre-defines all of our styling atoms and makes them accessible at `@components/theme`. + +```tsx +import { colors } from '@components/theme'; + +function YourComponent() { + return ( +
+ This div has a green background! +
+ ) +} +``` + +You can access the theme types at `@components/theme/types` and the theme config at `@components/theme/config`. + +### Writing Docs + +Our docs are generated using [Storybook](https://storybook.js.org/) and deployed to [Cloudfare](https://www.cloudflare.com/). + +- Storybook config is located at `.storybook` +- Static doc files are located at `alchemy-components/.docs` +- Component stories are located in each component directory:
`alchemy-components/components/Component/Component.stories.tsx` + +Storybook serves as our playground for developing components. You can start it locally: + +```bash +yarn storybook +``` + +This launches the docs app at `localhost:6006` and enables everything you need to quickly develop and document components. + +### Contributing + +Building a component library is a collaboriate effort! We're aiming to provide a first-class experience, so here's a list of the standards we'll be looking for: + +- Consitent prop and variant naming conventions:
+ -- `variant` is used to define style types, such as `outline` or `filled`.
+ -- `color` is used to define the components color, such as `violet` or `blue`.
+ -- `size` is used to define the components size, such as `xs` or `4xl`.
+ -- Booleans are prefixed with `is`: `isLoading` or `isDisabled`. +- All style props have a correseponding theme type, ie. `FontSizeOptions`. +- All components have an export of default props. +- Styles are defined using `style objects` instead of `tagged template literals`. +- Stories are organized into the correct directory . + +### FAQs + +- **How are components being styled?**
Our components are built using [Styled Components](https://styled-components.com/) that dynamically generate styles based on variant selection. diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.stories.tsx b/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.stories.tsx new file mode 100644 index 0000000000000..09d0d37f15421 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.stories.tsx @@ -0,0 +1,133 @@ +import { BADGE } from '@geometricpanda/storybook-addon-badges'; +import { GridList } from '@src/alchemy-components/.docs/mdx-components'; +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { Avatar, avatarDefaults } from './Avatar'; + +const IMAGE_URL = + 'https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/78/cb/e1/78cbe16d-28d9-057e-9f73-524c32eb5fe5/AppIcon-0-0-1x_U007emarketing-0-7-0-85-220.png/512x512bb.jpg'; + +// Auto Docs +const meta = { + title: 'Components / Avatar', + component: Avatar, + + // Display Properties + parameters: { + layout: 'centered', + badges: [BADGE.STABLE, 'readyForDesignReview'], + docs: { + subtitle: 'This component allows users to render a user pill with picture and name', + }, + }, + + // Component-level argTypes + argTypes: { + name: { + description: 'Name of the user.', + table: { + defaultValue: { summary: `${avatarDefaults.name}` }, + }, + control: 'text', + }, + imageUrl: { + description: 'URL of the user image.', + control: 'text', + }, + onClick: { + description: 'On click function for the Avatar.', + }, + size: { + description: 'Size of the Avatar.', + table: { + defaultValue: { summary: `${avatarDefaults.size}` }, + }, + control: 'select', + }, + showInPill: { + description: 'Whether Avatar is shown in pill format with name.', + table: { + defaultValue: { summary: `${avatarDefaults.showInPill}` }, + }, + control: 'boolean', + }, + + isOutlined: { + description: 'Whether Avatar is outlined.', + table: { + defaultValue: { summary: `${avatarDefaults.isOutlined}` }, + }, + control: 'boolean', + }, + }, + + // Define defaults + args: { + name: 'John Doe', + size: 'default', + showInPill: false, + isOutlined: false, + }, +} satisfies Meta; + +export default meta; + +// Stories + +type Story = StoryObj; + +// Basic story is what is displayed 1st in storybook & is used as the code sandbox +// Pass props to this so that it can be customized via the UI props panel +export const sandbox: Story = { + tags: ['dev'], + render: (props) => , +}; + +export const sizes = () => ( + + + + + + +); + +export const withImage = () => ( + + + + + + +); + +export const pills = () => ( + + + + + + + + + + + + + + +); + +export const outlined = () => ( + + + + +); + +export const withOnClick = () => ( + + window.alert('Avatar clicked')} /> + window.alert('Avatar clicked')} showInPill /> + +); diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.tsx b/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.tsx new file mode 100644 index 0000000000000..9e5ec025e08e3 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/Avatar.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import { AvatarImage, AvatarImageWrapper, AvatarText, Container } from './components'; +import { AvatarProps } from './types'; +import getAvatarColor, { getNameInitials } from './utils'; + +export const avatarDefaults: AvatarProps = { + name: 'User name', + size: 'default', + showInPill: false, + isOutlined: false, +}; + +export const Avatar = ({ + name = avatarDefaults.name, + imageUrl, + size = avatarDefaults.size, + onClick, + showInPill = avatarDefaults.showInPill, + isOutlined = avatarDefaults.isOutlined, +}: AvatarProps) => { + const [hasError, setHasError] = useState(false); + + return ( + + + {!hasError && imageUrl ? ( + setHasError(true)} /> + ) : ( + <>{getNameInitials(name)} + )} + + {showInPill && {name}} + + ); +}; diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/_tests_/getNameInitials.test.ts b/datahub-web-react/src/alchemy-components/components/Avatar/_tests_/getNameInitials.test.ts new file mode 100644 index 0000000000000..54bb258acb0d8 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/_tests_/getNameInitials.test.ts @@ -0,0 +1,34 @@ +import { getNameInitials } from '../utils'; + +describe('get initials of the name', () => { + it('get initials of name with first name and last name', () => { + expect(getNameInitials('John Doe ')).toEqual('JD'); + }); + it('get initials of name with first name and last name in lower case', () => { + expect(getNameInitials('john doe')).toEqual('JD'); + }); + it('get initials of name with only first name', () => { + expect(getNameInitials('Robert')).toEqual('RO'); + }); + it('get initials of name with only first name in lower case', () => { + expect(getNameInitials('robert')).toEqual('RO'); + }); + it('get initials of name with three names', () => { + expect(getNameInitials('James Edward Brown')).toEqual('JB'); + }); + it('get initials of name with four names', () => { + expect(getNameInitials('Michael James Alexander Scott')).toEqual('MS'); + }); + it('get initials of name with a hyphen', () => { + expect(getNameInitials('Mary-Jane Watson')).toEqual('MW'); + }); + it('get initials of name with an apostrophe', () => { + expect(getNameInitials("O'Connor")).toEqual('OC'); + }); + it('get initials of name with a single letter', () => { + expect(getNameInitials('J')).toEqual('J'); + }); + it('get initials of name with an empty string', () => { + expect(getNameInitials('')).toEqual(''); + }); +}); diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/components.ts b/datahub-web-react/src/alchemy-components/components/Avatar/components.ts new file mode 100644 index 0000000000000..bcd23a8ab086c --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/components.ts @@ -0,0 +1,51 @@ +import { colors } from '@src/alchemy-components/theme'; +import { AvatarSizeOptions } from '@src/alchemy-components/theme/config'; +import styled from 'styled-components'; +import { getAvatarColorStyles, getAvatarNameSizes, getAvatarSizes } from './utils'; + +export const Container = styled.div<{ $hasOnClick: boolean; $showInPill?: boolean }>` + display: inline-flex; + align-items: center; + gap: 4px; + border-radius: 20px; + border: ${(props) => props.$showInPill && `1px solid ${colors.gray[100]}`}; + padding: ${(props) => props.$showInPill && '3px 6px 3px 4px'}; + + ${(props) => + props.$hasOnClick && + ` + :hover { + cursor: pointer; + } + `} +`; + +export const AvatarImageWrapper = styled.div<{ + $color: string; + $size?: AvatarSizeOptions; + $isOutlined?: boolean; + $hasImage?: boolean; +}>` + ${(props) => getAvatarSizes(props.$size)} + + border-radius: 50%; + color: ${(props) => props.$color}; + border: ${(props) => props.$isOutlined && `1px solid ${colors.gray[1800]}`}; + display: flex; + align-items: center; + justify-content: center; + ${(props) => !props.$hasImage && getAvatarColorStyles(props.$color)} +`; + +export const AvatarImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +`; + +export const AvatarText = styled.span<{ $size?: AvatarSizeOptions }>` + color: ${colors.gray[1700]}; + font-weight: 600; + font-size: ${(props) => getAvatarNameSizes(props.$size)}; +`; diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/index.ts b/datahub-web-react/src/alchemy-components/components/Avatar/index.ts new file mode 100644 index 0000000000000..d3fb6dfa7c09e --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/index.ts @@ -0,0 +1 @@ +export { Avatar } from './Avatar'; diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/types.ts b/datahub-web-react/src/alchemy-components/components/Avatar/types.ts new file mode 100644 index 0000000000000..98c554b620dcb --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/types.ts @@ -0,0 +1,10 @@ +import { AvatarSizeOptions } from '@src/alchemy-components/theme/config'; + +export interface AvatarProps { + name: string; + imageUrl?: string; + onClick?: () => void; + size?: AvatarSizeOptions; + showInPill?: boolean; + isOutlined?: boolean; +} diff --git a/datahub-web-react/src/alchemy-components/components/Avatar/utils.ts b/datahub-web-react/src/alchemy-components/components/Avatar/utils.ts new file mode 100644 index 0000000000000..46b2ee25488b8 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Avatar/utils.ts @@ -0,0 +1,64 @@ +import { colors } from '@src/alchemy-components/theme'; + +export const getNameInitials = (userName: string) => { + if (!userName) return ''; + const names = userName.trim().split(/[\s']+/); // Split by spaces or apostrophes + if (names.length === 1) { + const firstName = names[0]; + return firstName.length > 1 ? firstName[0]?.toUpperCase() + firstName[1]?.toUpperCase() : firstName[0]; + } + return names[0][0]?.toUpperCase() + names[names.length - 1][0]?.toUpperCase() || ''; +}; + +export function hashString(str: string) { + let hash = 0; + if (str.length === 0) { + return hash; + } + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + // eslint-disable-next-line + hash = (hash << 5) - hash + char; + // eslint-disable-next-line + hash = hash & hash; // Convert to 32bit integer + } + return Math.abs(hash); +} + +const colorMap = { + [colors.violet[500]]: { backgroundColor: colors.gray[1000], border: `1px solid ${colors.violet[1000]}` }, + [colors.blue[1000]]: { backgroundColor: colors.gray[1100], border: `1px solid ${colors.blue[200]}` }, + [colors.gray[600]]: { backgroundColor: colors.gray[1500], border: `1px solid ${colors.gray[100]}` }, +}; + +const avatarColors = Object.keys(colorMap); + +export const getAvatarColorStyles = (color) => { + return { + ...colorMap[color], + }; +}; + +export default function getAvatarColor(name: string) { + return avatarColors[hashString(name) % avatarColors.length]; +} + +export const getAvatarSizes = (size) => { + const sizeMap = { + sm: { width: '18px', height: '18px', fontSize: '8px' }, + md: { width: '24px', height: '24px', fontSize: '12px' }, + lg: { width: '28px', height: '28px', fontSize: '14px' }, + default: { width: '20px', height: '20px', fontSize: '10px' }, + }; + + return { + ...sizeMap[size], + }; +}; + +export const getAvatarNameSizes = (size) => { + if (size === 'lg') return '16px'; + if (size === 'sm') return '10px'; + if (size === 'md') return '14px'; + return '12px'; +}; diff --git a/datahub-web-react/src/alchemy-components/components/Badge/Badge.stories.tsx b/datahub-web-react/src/alchemy-components/components/Badge/Badge.stories.tsx new file mode 100644 index 0000000000000..88d499226feaf --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/Badge.stories.tsx @@ -0,0 +1,102 @@ +import React from 'react'; + +import { BADGE } from '@geometricpanda/storybook-addon-badges'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { GridList } from '@components/.docs/mdx-components'; +import { Badge, badgeDefault } from './Badge'; +import pillMeta from '../Pills/Pill.stories'; +import { omitKeys } from './utils'; + +const pillMetaArgTypes = omitKeys(pillMeta.argTypes, ['label']); +const pillMetaArgs = omitKeys(pillMeta.args, ['label']); + +const meta = { + title: 'Components / Badge', + component: Badge, + + // Display Properties + parameters: { + layout: 'centered', + badges: [BADGE.EXPERIMENTAL], + docs: { + subtitle: 'A component that is used to get badge', + }, + }, + + // Component-level argTypes + argTypes: { + count: { + description: 'Count to show.', + table: { + defaultValue: { summary: `${badgeDefault.count}` }, + }, + control: { + type: 'number', + }, + }, + overflowCount: { + description: 'Max count to show.', + table: { + defaultValue: { summary: `${badgeDefault.overflowCount}` }, + }, + control: { + type: 'number', + }, + }, + showZero: { + description: 'Whether to show badge when `count` is zero.', + table: { + defaultValue: { summary: `${badgeDefault.showZero}` }, + }, + control: { + type: 'boolean', + }, + }, + ...pillMetaArgTypes, + }, + + // Define defaults + args: { + count: 100, + overflowCount: badgeDefault.overflowCount, + showZero: badgeDefault.showZero, + ...pillMetaArgs, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const sandbox: Story = { + tags: ['dev'], + render: (props) => , +}; + +export const sizes = () => ( + + + + + +); + +export const colors = () => ( + + + + + + + + +); + +export const withIcon = () => ( + + + + + +); diff --git a/datahub-web-react/src/alchemy-components/components/Badge/Badge.tsx b/datahub-web-react/src/alchemy-components/components/Badge/Badge.tsx new file mode 100644 index 0000000000000..1c934ef120eee --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/Badge.tsx @@ -0,0 +1,29 @@ +import { Pill } from '@components'; +import React, { useMemo } from 'react'; + +import { BadgeProps } from './types'; +import { formatBadgeValue } from './utils'; +import { BadgeContainer } from './components'; + +export const badgeDefault: BadgeProps = { + count: 0, + overflowCount: 99, + showZero: false, +}; + +export function Badge({ + count = badgeDefault.count, + overflowCount = badgeDefault.overflowCount, + showZero = badgeDefault.showZero, + ...props +}: BadgeProps) { + const label = useMemo(() => formatBadgeValue(count, overflowCount), [count, overflowCount]); + + if (!showZero && count === 0) return null; + + return ( + + + + ); +} diff --git a/datahub-web-react/src/alchemy-components/components/Badge/components.ts b/datahub-web-react/src/alchemy-components/components/Badge/components.ts new file mode 100644 index 0000000000000..a7791cd4f5ff8 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/components.ts @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const BadgeContainer = styled.div({ + // Base root styles + display: 'inline-flex', +}); diff --git a/datahub-web-react/src/alchemy-components/components/Badge/index.ts b/datahub-web-react/src/alchemy-components/components/Badge/index.ts new file mode 100644 index 0000000000000..26a9e305c7ffd --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/index.ts @@ -0,0 +1 @@ +export { Badge } from './Badge'; diff --git a/datahub-web-react/src/alchemy-components/components/Badge/types.ts b/datahub-web-react/src/alchemy-components/components/Badge/types.ts new file mode 100644 index 0000000000000..21348f2a08341 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/types.ts @@ -0,0 +1,8 @@ +import { HTMLAttributes } from 'react'; +import { PillProps } from '../Pills/types'; + +export interface BadgeProps extends HTMLAttributes, Omit { + count: number; + overflowCount?: number; + showZero?: boolean; +} diff --git a/datahub-web-react/src/alchemy-components/components/Badge/utils.ts b/datahub-web-react/src/alchemy-components/components/Badge/utils.ts new file mode 100644 index 0000000000000..e59ec2af998e7 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Badge/utils.ts @@ -0,0 +1,15 @@ +export const formatBadgeValue = (value: number, overflowCount?: number): string => { + if (overflowCount === undefined || value < overflowCount) return String(value); + + return `${overflowCount}+`; +}; + +export function omitKeys(obj: T, keys: K[]): Omit { + const { ...rest } = obj; + + keys.forEach((key) => { + delete rest[key]; + }); + + return rest; +} diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.stories.tsx b/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.stories.tsx new file mode 100644 index 0000000000000..1258ff398c0a7 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.stories.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { BADGE } from '@geometricpanda/storybook-addon-badges'; +import type { Meta, StoryObj } from '@storybook/react'; +import { BarChart } from './BarChart'; +import { getMockedProps } from './utils'; + +const meta = { + title: 'Charts / BarChart', + component: BarChart, + + // Display Properties + parameters: { + layout: 'centered', + badges: [BADGE.EXPERIMENTAL], + docs: { + subtitle: 'A component that is used to show BarChart', + }, + }, + + // Component-level argTypes + argTypes: { + data: { + description: 'Array of datum to show', + }, + xAccessor: { + description: 'A function to convert datum to value of X', + }, + yAccessor: { + description: 'A function to convert datum to value of Y', + }, + renderTooltipContent: { + description: 'A function to replace default rendering of toolbar', + }, + margin: { + description: 'Add margins to chart', + }, + leftAxisTickFormat: { + description: 'A function to format labels of left axis', + }, + leftAxisTickLabelProps: { + description: 'Props for label of left axis', + }, + bottomAxisTickFormat: { + description: 'A function to format labels of bottom axis', + }, + bottomAxisTickLabelProps: { + description: 'Props for label of bottom axis', + }, + barColor: { + description: 'Color of bar', + control: { + type: 'color', + }, + }, + barSelectedColor: { + description: 'Color of selected bar', + control: { + type: 'color', + }, + }, + gridColor: { + description: "Color of grid's lines", + control: { + type: 'color', + }, + }, + renderGradients: { + description: 'A function to render different gradients that can be used as colors', + }, + }, + + // Define defaults + args: { + ...getMockedProps(), + renderTooltipContent: (datum) => <>DATUM: {JSON.stringify(datum)}, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const sandbox: Story = { + tags: ['dev'], + render: (props) => ( +
+ +
+ ), +}; diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.tsx b/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.tsx new file mode 100644 index 0000000000000..f6a9e4d572bb7 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/BarChart.tsx @@ -0,0 +1,152 @@ +import React, { useState } from 'react'; +import { colors } from '@src/alchemy-components/theme'; +import { abbreviateNumber } from '@src/app/dataviz/utils'; +import { TickLabelProps } from '@visx/axis'; +import { LinearGradient } from '@visx/gradient'; +import { ParentSize } from '@visx/responsive'; +import { Axis, AxisScale, BarSeries, Grid, Tooltip, XYChart } from '@visx/xychart'; +import dayjs from 'dayjs'; +import { Popover } from '../Popover'; +import { ChartWrapper, StyledBarSeries } from './components'; +import { BarChartProps } from './types'; + +const commonTickLabelProps: TickLabelProps = { + fontSize: 10, + fontFamily: 'Mulish', + fill: colors.gray[1700], +}; + +export const barChartDefault: BarChartProps = { + data: [], + xAccessor: (datum) => datum?.x, + yAccessor: (datum) => datum?.y, + leftAxisTickFormat: abbreviateNumber, + leftAxisTickLabelProps: { + ...commonTickLabelProps, + textAnchor: 'end', + }, + bottomAxisTickFormat: (value) => dayjs(value).format('DD MMM'), + bottomAxisTickLabelProps: { + ...commonTickLabelProps, + textAnchor: 'middle', + verticalAnchor: 'start', + width: 20, + }, + barColor: 'url(#bar-gradient)', + barSelectedColor: colors.violet[500], + gridColor: '#e0e0e0', + renderGradients: () => , +}; + +export function BarChart({ + data, + xAccessor = barChartDefault.xAccessor, + yAccessor = barChartDefault.yAccessor, + renderTooltipContent, + margin, + leftAxisTickFormat = barChartDefault.leftAxisTickFormat, + leftAxisTickLabelProps = barChartDefault.leftAxisTickLabelProps, + bottomAxisTickFormat = barChartDefault.bottomAxisTickFormat, + bottomAxisTickLabelProps = barChartDefault.bottomAxisTickLabelProps, + barColor = barChartDefault.barColor, + barSelectedColor = barChartDefault.barSelectedColor, + gridColor = barChartDefault.gridColor, + renderGradients = barChartDefault.renderGradients, +}: BarChartProps) { + const [hasSelectedBar, setHasSelectedBar] = useState(false); + + // FYI: additional margins to show left and bottom axises + const internalMargin = { + top: (margin?.top ?? 0) + 30, + right: margin?.right ?? 0, + bottom: (margin?.bottom ?? 0) + 35, + left: (margin?.left ?? 0) + 40, + }; + + const accessors = { xAccessor, yAccessor }; + + return ( + + + {({ width, height }) => { + return ( + + {renderGradients?.()} + + + + + + + + + + } + $hasSelectedItem={hasSelectedBar} + $color={barColor} + $selectedColor={barSelectedColor} + dataKey="bar-seria-0" + data={data} + radius={4} + radiusTop + onBlur={() => setHasSelectedBar(false)} + onFocus={() => setHasSelectedBar(true)} + // Internally the library doesn't emmit these events if handlers are empty + // They are requred to show/hide/move tooltip + onPointerMove={() => null} + onPointerUp={() => null} + onPointerOut={() => null} + {...accessors} + /> + + + snapTooltipToDatumX + snapTooltipToDatumY + unstyled + applyPositionStyle + renderTooltip={({ tooltipData }) => { + return ( + tooltipData?.nearestDatum && ( + + ) + ); + }} + /> + + ); + }} + + + ); +} diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/components.tsx b/datahub-web-react/src/alchemy-components/components/BarChart/components.tsx new file mode 100644 index 0000000000000..aa8f1320ef21d --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/components.tsx @@ -0,0 +1,34 @@ +import { colors } from '@src/alchemy-components/theme'; +import { BarSeries } from '@visx/xychart'; +import styled from 'styled-components'; + +export const ChartWrapper = styled.div` + width: 100%; + height: 100%; + position: relative; +`; + +export const StyledBarSeries = styled(BarSeries)<{ + $hasSelectedItem?: boolean; + $color?: string; + $selectedColor?: string; +}>` + & { + cursor: pointer; + + fill: ${(props) => (props.$hasSelectedItem ? props.$selectedColor : props.$color) || colors.violet[500]}; + ${(props) => props.$hasSelectedItem && 'opacity: 0.3;'} + + :hover { + fill: ${(props) => props.$selectedColor || colors.violet[500]}; + filter: drop-shadow(0px -2px 5px rgba(33, 23, 95, 0.3)); + opacity: 1; + } + + :focus { + fill: ${(props) => props.$selectedColor || colors.violet[500]}; + outline: none; + opacity: 1; + } + } +`; diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/index.ts b/datahub-web-react/src/alchemy-components/components/BarChart/index.ts new file mode 100644 index 0000000000000..fdfc3f3ab44a8 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/index.ts @@ -0,0 +1 @@ +export { BarChart } from './BarChart'; diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/types.ts b/datahub-web-react/src/alchemy-components/components/BarChart/types.ts new file mode 100644 index 0000000000000..5fd7e2e63e241 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/types.ts @@ -0,0 +1,18 @@ +import { TickFormatter, TickLabelProps } from '@visx/axis'; +import { Margin } from '@visx/xychart'; + +export type BarChartProps = { + data: DatumType[]; + xAccessor: (datum: DatumType) => string | number; + yAccessor: (datum: DatumType) => number; + renderTooltipContent?: (datum: DatumType) => React.ReactNode; + margin?: Margin; + leftAxisTickFormat?: TickFormatter; + leftAxisTickLabelProps?: TickLabelProps; + bottomAxisTickFormat?: TickFormatter; + bottomAxisTickLabelProps?: TickLabelProps; + barColor?: string; + barSelectedColor?: string; + gridColor?: string; + renderGradients?: () => React.ReactNode; +}; diff --git a/datahub-web-react/src/alchemy-components/components/BarChart/utils.ts b/datahub-web-react/src/alchemy-components/components/BarChart/utils.ts new file mode 100644 index 0000000000000..0b592da7f59b0 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/BarChart/utils.ts @@ -0,0 +1,26 @@ +import dayjs from 'dayjs'; + +export function generateMockData(length = 30, maxValue = 50_000, minValue = 0) { + return Array(length) + .fill(0) + .map((_, index) => { + const date = dayjs() + .startOf('day') + .add(index - length, 'days') + .toDate(); + const value = Math.max(Math.random() * maxValue, minValue); + + return { + x: date, + y: value, + }; + }); +} + +export function getMockedProps() { + return { + data: generateMockData(), + xAccessor: (datum) => datum.x, + yAccessor: (datum) => Math.max(datum.y, 1000), + }; +} diff --git a/datahub-web-react/src/alchemy-components/components/Button/Button.stories.tsx b/datahub-web-react/src/alchemy-components/components/Button/Button.stories.tsx new file mode 100644 index 0000000000000..e2d7c2852da51 --- /dev/null +++ b/datahub-web-react/src/alchemy-components/components/Button/Button.stories.tsx @@ -0,0 +1,203 @@ +import React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; +import { BADGE } from '@geometricpanda/storybook-addon-badges'; + +import { GridList } from '@components/.docs/mdx-components'; +import { AVAILABLE_ICONS } from '@components'; + +import { Button, buttonDefaults } from '.'; + +// Auto Docs +const meta = { + title: 'Forms / Button', + component: Button, + + // Display Properties + parameters: { + layout: 'centered', + badges: [BADGE.STABLE, 'readyForDesignReview'], + docs: { + subtitle: + 'Buttons are used to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation.', + }, + }, + + // Component-level argTypes + argTypes: { + children: { + description: 'The content of the Button.', + control: { + type: 'text', + }, + }, + variant: { + description: 'The variant of the Button.', + options: ['filled', 'outline', 'text'], + table: { + defaultValue: { summary: buttonDefaults.variant }, + }, + control: { + type: 'radio', + }, + }, + color: { + description: 'The color of the Button.', + options: ['violet', 'green', 'red', 'blue', 'gray'], + table: { + defaultValue: { summary: buttonDefaults.color }, + }, + control: { + type: 'select', + }, + }, + size: { + description: 'The size of the Button.', + options: ['sm', 'md', 'lg', 'xl'], + table: { + defaultValue: { summary: buttonDefaults.size }, + }, + control: { + type: 'select', + }, + }, + icon: { + description: 'The icon to display in the Button.', + type: 'string', + options: AVAILABLE_ICONS, + table: { + defaultValue: { summary: 'undefined' }, + }, + control: { + type: 'select', + }, + }, + iconPosition: { + description: 'The position of the icon in the Button.', + options: ['left', 'right'], + table: { + defaultValue: { summary: buttonDefaults.iconPosition }, + }, + control: { + type: 'radio', + }, + }, + isCircle: { + description: + 'Whether the Button should be a circle. If this is selected, the Button will ignore children content, so add an Icon to the Button.', + table: { + defaultValue: { summary: buttonDefaults?.isCircle?.toString() }, + }, + control: { + type: 'boolean', + }, + }, + isLoading: { + description: 'Whether the Button is in a loading state.', + table: { + defaultValue: { summary: buttonDefaults?.isLoading?.toString() }, + }, + control: { + type: 'boolean', + }, + }, + isDisabled: { + description: 'Whether the Button is disabled.', + table: { + defaultValue: { summary: buttonDefaults?.isDisabled?.toString() }, + }, + control: { + type: 'boolean', + }, + }, + isActive: { + description: 'Whether the Button is active.', + table: { + defaultValue: { summary: buttonDefaults?.isActive?.toString() }, + }, + control: { + type: 'boolean', + }, + }, + onClick: { + description: 'Function to call when the button is clicked', + table: { + defaultValue: { summary: 'undefined' }, + }, + action: 'clicked', + }, + }, + + // Define defaults + args: { + children: 'Button Content', + variant: buttonDefaults.variant, + color: buttonDefaults.color, + size: buttonDefaults.size, + icon: undefined, + iconPosition: buttonDefaults.iconPosition, + isCircle: buttonDefaults.isCircle, + isLoading: buttonDefaults.isLoading, + isDisabled: buttonDefaults.isDisabled, + isActive: buttonDefaults.isActive, + onClick: () => console.log('Button clicked'), + }, +} satisfies Meta; + +export default meta; + +// Stories + +type Story = StoryObj; + +// Basic story is what is displayed 1st in storybook & is used as the code sandbox +// Pass props to this so that it can be customized via the UI props panel +export const sandbox: Story = { + tags: ['dev'], + render: (props) => , +}; + +export const states = () => ( + + + + + + +); + +export const colors = () => ( + + + + + + + +); + +export const sizes = () => ( + + + + + + +); + +export const withIcon = () => ( + + + + +); + +export const circleShape = () => ( + +