From 377f95c9db93712c1f772bab0ac30fbfc3103f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Mon, 6 Nov 2023 23:14:47 +0100 Subject: [PATCH] feat: add SettingsObjectFieldPreview and SettingsObjectFieldPreviewCard (#2376) * feat: add SettingsObjectFieldPreview Closes #2343 * feat: add SettingsObjectFieldPreviewCard Closes #2349 * Fix ci * Fix tests * Fix tests --------- Co-authored-by: Charles Bochet --- .../components/SettingsObjectFieldPreview.tsx | 105 ++++++++++++++++++ .../SettingsObjectFieldPreviewCard.tsx | 52 +++++++++ .../SettingsObjectFieldPreview.stories.tsx | 26 +++++ ...SettingsObjectFieldPreviewCard.stories.tsx | 34 ++++++ .../modules/ui/input/hooks/useLazyLoadIcon.ts | 15 +-- 5 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx create mode 100644 front/src/modules/settings/data-model/components/SettingsObjectFieldPreviewCard.tsx create mode 100644 front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx create mode 100644 front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreviewCard.stories.tsx diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx new file mode 100644 index 000000000..069275f5e --- /dev/null +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreview.tsx @@ -0,0 +1,105 @@ +import { ReactNode } from 'react'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { Tag } from '@/ui/display/tag/components/Tag'; +import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon'; + +type SettingsObjectFieldPreviewProps = { + objectIconKey: string; + objectLabelPlural: string; + isObjectCustom: boolean; + fieldIconKey: string; + fieldLabel: string; + fieldValue: ReactNode; +}; + +const StyledContainer = styled.div` + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.md}; + box-sizing: border-box; + color: ${({ theme }) => theme.font.color.primary}; + max-width: 480px; + padding: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledObjectSummary = styled.div` + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledObjectName = styled.div` + align-items: center; + display: flex; + font-weight: ${({ theme }) => theme.font.weight.medium}; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledFieldPreview = styled.div` + align-items: center; + background-color: ${({ theme }) => theme.background.primary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + height: ${({ theme }) => theme.spacing(8)}; + overflow: hidden; + padding-left: ${({ theme }) => theme.spacing(2)}; + white-space: nowrap; +`; + +const StyledFieldLabel = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +export const SettingsObjectFieldPreview = ({ + objectIconKey, + objectLabelPlural, + isObjectCustom, + fieldIconKey, + fieldLabel, + fieldValue, +}: SettingsObjectFieldPreviewProps) => { + const theme = useTheme(); + const { Icon: ObjectIcon } = useLazyLoadIcon(objectIconKey); + const { Icon: FieldIcon } = useLazyLoadIcon(fieldIconKey); + + return ( + + + + {!!ObjectIcon && ( + + )} + {objectLabelPlural} + + {isObjectCustom ? ( + + ) : ( + + )} + + + + {!!FieldIcon && ( + + )} + {fieldLabel}: + + {fieldValue} + + + ); +}; diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldPreviewCard.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreviewCard.tsx new file mode 100644 index 000000000..48eab6686 --- /dev/null +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldPreviewCard.tsx @@ -0,0 +1,52 @@ +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; + +type SettingsObjectFieldPreviewCardProps = { + preview: ReactNode; + form?: ReactNode; +}; + +const StyledPreviewContainer = styled.div` + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + padding: ${({ theme }) => theme.spacing(4)}; + + &:not(:last-child) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +`; + +const StyledTitle = styled.h3` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + margin: 0; + margin-bottom: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledFormContainer = styled.div` + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + border-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + padding: ${({ theme }) => theme.spacing(4)}; +`; + +export const SettingsObjectFieldPreviewCard = ({ + preview, + form, +}: SettingsObjectFieldPreviewCardProps) => { + return ( +
+ + Preview + {preview} + + {!!form && {form}} +
+ ); +}; diff --git a/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx new file mode 100644 index 000000000..509ca9d9b --- /dev/null +++ b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreview.stories.tsx @@ -0,0 +1,26 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +import { SettingsObjectFieldPreview } from '../SettingsObjectFieldPreview'; + +const meta: Meta = { + title: 'Modules/Settings/DataModel/SettingsObjectFieldPreview', + component: SettingsObjectFieldPreview, + decorators: [ComponentDecorator], + args: { + objectIconKey: 'IconUser', + objectLabelPlural: 'People', + fieldIconKey: 'IconNotes', + fieldLabel: 'Description', + fieldValue: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.', + }, +}; + +export default meta; +type Story = StoryObj; + +export const StandardObject: Story = { args: { isObjectCustom: false } }; + +export const CustomObject: Story = { args: { isObjectCustom: true } }; diff --git a/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreviewCard.stories.tsx b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreviewCard.stories.tsx new file mode 100644 index 000000000..ed317d58c --- /dev/null +++ b/front/src/modules/settings/data-model/components/__stories__/SettingsObjectFieldPreviewCard.stories.tsx @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { TextInput } from '@/ui/input/components/TextInput'; +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +import { SettingsObjectFieldPreview } from '../SettingsObjectFieldPreview'; +import { SettingsObjectFieldPreviewCard } from '../SettingsObjectFieldPreviewCard'; + +const meta: Meta = { + title: 'Modules/Settings/DataModel/SettingsObjectFieldPreviewCard', + component: SettingsObjectFieldPreviewCard, + decorators: [ComponentDecorator], + args: { + preview: ( + + ), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithForm: Story = { + args: { form: }, +}; diff --git a/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts b/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts index 79de15fef..a6be3ab23 100644 --- a/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts +++ b/front/src/modules/ui/input/hooks/useLazyLoadIcon.ts @@ -2,20 +2,21 @@ import { useEffect, useState } from 'react'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; +import { useLazyLoadIcons } from './useLazyLoadIcons'; + export const useLazyLoadIcon = (iconKey: string) => { + const { isLoadingIcons, icons } = useLazyLoadIcons(); const [Icon, setIcon] = useState(); const [isLoadingIcon, setIsLoadingIcon] = useState(true); useEffect(() => { if (!iconKey) return; - import(`@tabler/icons-react/dist/esm/icons/${iconKey}.js`).then( - (lazyLoadedIcon) => { - setIcon(lazyLoadedIcon.default); - setIsLoadingIcon(false); - }, - ); - }, [iconKey]); + if (!isLoadingIcons) { + setIcon(icons[iconKey]); + setIsLoadingIcon(false); + } + }, [iconKey, icons, isLoadingIcons]); return { Icon, isLoadingIcon }; };