diff --git a/.changeset/great-papers-yawn.md b/.changeset/great-papers-yawn.md new file mode 100644 index 000000000..076636aeb --- /dev/null +++ b/.changeset/great-papers-yawn.md @@ -0,0 +1,5 @@ +--- +'@drivenets/design-system': patch +--- + +Add `DsIllustration` component diff --git a/CONTEXT.md b/CONTEXT.md index 8a88f7e2a..c470b9f3c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -48,6 +48,14 @@ _Avoid_: sub props, child props Content a **Component** renders when it has genuinely zero data items. Distinct from a loading/skeleton state and from a transient frame where a virtualized body has not yet produced rows. Gate it on data count, never on rendered/virtual row count. _Avoid_: no-data view, blank state, placeholder +**Icon**: +A monochrome glyph rendered by `DsIcon` — Material Symbols or a `special-*` custom SVG — at 16–32px, tinted with `currentColor`. +_Avoid_: rich icon, state icon (those tiles are **Illustrations**) + +**Illustration**: +A 250×200 colorful empty-screen tile rendered by `DsIllustration` (clouds plus a scene). Not an **Icon**. +_Avoid_: rich icon, DAP_State Icon (as a code name), Cluster NE (for the Device tile) + **Successor component**: A new **Component** folder that supersedes an older one while the old export remains (often `ds-{name}-v2` or `ds-{name}-v3`). Mark the old API `@deprecated` in types and stories; prefer the successor in new code. _Avoid_: replacement, v2 component (as a synonym for the pattern name) diff --git a/packages/design-system/src/components/ds-illustration/__tests__/__snapshots__/ds-illustration.docs.snap b/packages/design-system/src/components/ds-illustration/__tests__/__snapshots__/ds-illustration.docs.snap new file mode 100644 index 000000000..0a5199200 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/__tests__/__snapshots__/ds-illustration.docs.snap @@ -0,0 +1,9 @@ +# DsIllustration docs snippets + +## Default + +### Show code + + +### MCP manifest +const Default = () => ; \ No newline at end of file diff --git a/packages/design-system/src/components/ds-illustration/__tests__/ds-illustration.browser.test.tsx b/packages/design-system/src/components/ds-illustration/__tests__/ds-illustration.browser.test.tsx new file mode 100644 index 000000000..e1c491a05 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/__tests__/ds-illustration.browser.test.tsx @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { page } from 'vitest/browser'; +import { DsIllustration } from '../index'; + +describe('DsIllustration', () => { + it('is decorative by default and exposes a label when asked', async () => { + const { container, rerender } = await page.render(); + const svg = container.querySelector('svg'); + + expect(svg).toHaveAttribute('aria-hidden', 'true'); + + await rerender(); + + await expect.element(page.getByLabelText('Device scene')).toBeVisible(); + }); +}); diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.figma.ts b/packages/design-system/src/components/ds-illustration/ds-illustration.figma.ts new file mode 100644 index 000000000..c9f483a0b --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.figma.ts @@ -0,0 +1,34 @@ +// url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=27983-34300 +// source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-illustration +// component=DsIllustration +import figma from 'figma'; + +const instance = figma.selectedInstance; + +const variant = + instance.getEnum('Property 1', { + Done: 'done', + EmptyInbox: 'empty-inbox', + Error: 'error', + NoConnection: 'no-connection', + NoCreditCard: 'no-credit-card', + NoDocuments: 'no-documents', + NoGPS: 'no-gps', + NoImages: 'no-images', + NoItemsCart: 'no-items-cart', + NoMessages: 'no-messages', + NoSearchResult: 'no-search-result', + NoTasks: 'no-tasks', + 'No Device': 'no-device', + 'Go Filter': 'go-filter', + Device: 'device', + Site: 'site', + configuration: 'configuration', + }) ?? 'no-tasks'; + +export default { + example: figma.code``, + imports: ["import { DsIllustration } from '@drivenets/design-system';"], + id: 'ds-illustration', + metadata: { nestable: true }, +} satisfies figma.Template; diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.module.scss b/packages/design-system/src/components/ds-illustration/ds-illustration.module.scss new file mode 100644 index 000000000..cb5d03dc3 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.module.scss @@ -0,0 +1,6 @@ +.root { + display: block; + width: 250px; + height: 200px; + max-width: 100%; +} diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.stories.module.scss b/packages/design-system/src/components/ds-illustration/ds-illustration.stories.module.scss new file mode 100644 index 000000000..f85e538d8 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.stories.module.scss @@ -0,0 +1,6 @@ +.showcase { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--standard); + padding: var(--standard); +} diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.stories.tsx b/packages/design-system/src/components/ds-illustration/ds-illustration.stories.tsx new file mode 100644 index 000000000..042e5c1f9 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { DsTypography } from '../ds-typography'; +import { DsIllustration } from './index'; +import { dsIllustrationVariants } from './ds-illustration.types'; +import styles from './ds-illustration.stories.module.scss'; +import { DsStack } from '../ds-stack'; + +const meta: Meta = { + title: 'Components/Illustration', + component: DsIllustration, + parameters: { layout: 'centered' }, + argTypes: { + variant: { control: 'select', options: dsIllustrationVariants }, + className: { table: { disable: true } }, + style: { table: { disable: true } }, + ref: { table: { disable: true } }, + 'aria-hidden': { table: { disable: true } }, + 'aria-label': { table: { disable: true } }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + variant: 'no-tasks', + }, +}; + +/** + * Every published tile. Visual catalog only — pick a `variant` in product code. + */ +export const Showcase: Story = { + tags: ['!manifest'], + parameters: { + layout: 'fullscreen', + docs: { canvas: { sourceState: 'none' } }, + }, + render: () => ( +
+ {dsIllustrationVariants.map((variant) => ( + + + + {variant} + + + ))} +
+ ), +}; diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.tsx b/packages/design-system/src/components/ds-illustration/ds-illustration.tsx new file mode 100644 index 000000000..78b2ab673 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.tsx @@ -0,0 +1,29 @@ +import classNames from 'classnames'; +import styles from './ds-illustration.module.scss'; +import { illustrations } from './illustrations'; +import type { DsIllustrationProps } from './ds-illustration.types'; + +const DsIllustration = ({ + variant = 'no-tasks', + className, + style, + ref, + 'aria-hidden': ariaHidden = true, + 'aria-label': ariaLabel, +}: DsIllustrationProps) => { + const Svg = illustrations[variant]; + + return ( + + ); +}; + +DsIllustration.displayName = 'DsIllustration'; + +export default DsIllustration; diff --git a/packages/design-system/src/components/ds-illustration/ds-illustration.types.ts b/packages/design-system/src/components/ds-illustration/ds-illustration.types.ts new file mode 100644 index 000000000..661da713d --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/ds-illustration.types.ts @@ -0,0 +1,45 @@ +import type { CSSProperties, Ref } from 'react'; + +export const dsIllustrationVariants = [ + 'configuration', + 'device', + 'done', + 'empty-inbox', + 'error', + 'go-filter', + 'no-connection', + 'no-credit-card', + 'no-device', + 'no-documents', + 'no-gps', + 'no-images', + 'no-items-cart', + 'no-messages', + 'no-search-result', + 'no-tasks', + 'site', +] as const; + +export type DsIllustrationVariant = (typeof dsIllustrationVariants)[number]; + +export interface DsIllustrationProps { + /** + * Which empty-screen tile to render. + * @default 'no-tasks' + */ + variant?: DsIllustrationVariant; + + className?: string; + + style?: CSSProperties; + + ref?: Ref; + + /** + * Decorative by default. Set `false` with `aria-label` when the tile is informative. + * @default true + */ + 'aria-hidden'?: boolean | 'true' | 'false'; + + 'aria-label'?: string; +} diff --git a/packages/design-system/src/components/ds-illustration/illustrations/configuration.tsx b/packages/design-system/src/components/ds-illustration/illustrations/configuration.tsx new file mode 100644 index 000000000..cf4f06329 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/configuration.tsx @@ -0,0 +1,51 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const ConfigurationIllustration: FC> = (props) => ( + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/device.tsx b/packages/design-system/src/components/ds-illustration/illustrations/device.tsx new file mode 100644 index 000000000..9140d39ff --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/device.tsx @@ -0,0 +1,133 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const DeviceIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/done.tsx b/packages/design-system/src/components/ds-illustration/illustrations/done.tsx new file mode 100644 index 000000000..2dfe3c9bc --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/done.tsx @@ -0,0 +1,107 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const DoneIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/empty-inbox.tsx b/packages/design-system/src/components/ds-illustration/illustrations/empty-inbox.tsx new file mode 100644 index 000000000..3f1f099f2 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/empty-inbox.tsx @@ -0,0 +1,53 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const EmptyInboxIllustration: FC> = (props) => ( + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/error.tsx b/packages/design-system/src/components/ds-illustration/illustrations/error.tsx new file mode 100644 index 000000000..dd84f3693 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/error.tsx @@ -0,0 +1,86 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const ErrorIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/go-filter.tsx b/packages/design-system/src/components/ds-illustration/illustrations/go-filter.tsx new file mode 100644 index 000000000..8217964c2 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/go-filter.tsx @@ -0,0 +1,51 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const GoFilterIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/illustration-clouds.tsx b/packages/design-system/src/components/ds-illustration/illustrations/illustration-clouds.tsx new file mode 100644 index 000000000..bee58de6e --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/illustration-clouds.tsx @@ -0,0 +1,10 @@ +import type { FC } from 'react'; + +export const IllustrationClouds: FC = () => ( + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/index.ts b/packages/design-system/src/components/ds-illustration/illustrations/index.ts new file mode 100644 index 000000000..3dd828b1e --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/index.ts @@ -0,0 +1,39 @@ +import type { FC, SVGProps } from 'react'; +import type { DsIllustrationVariant } from '../ds-illustration.types'; +import { ConfigurationIllustration } from './configuration'; +import { DeviceIllustration } from './device'; +import { DoneIllustration } from './done'; +import { EmptyInboxIllustration } from './empty-inbox'; +import { ErrorIllustration } from './error'; +import { GoFilterIllustration } from './go-filter'; +import { NoConnectionIllustration } from './no-connection'; +import { NoCreditCardIllustration } from './no-credit-card'; +import { NoDeviceIllustration } from './no-device'; +import { NoDocumentsIllustration } from './no-documents'; +import { NoGpsIllustration } from './no-gps'; +import { NoImagesIllustration } from './no-images'; +import { NoItemsCartIllustration } from './no-items-cart'; +import { NoMessagesIllustration } from './no-messages'; +import { NoSearchResultIllustration } from './no-search-result'; +import { NoTasksIllustration } from './no-tasks'; +import { SiteIllustration } from './site'; + +export const illustrations: Record>> = Object.freeze({ + configuration: ConfigurationIllustration, + device: DeviceIllustration, + done: DoneIllustration, + 'empty-inbox': EmptyInboxIllustration, + error: ErrorIllustration, + 'go-filter': GoFilterIllustration, + 'no-connection': NoConnectionIllustration, + 'no-credit-card': NoCreditCardIllustration, + 'no-device': NoDeviceIllustration, + 'no-documents': NoDocumentsIllustration, + 'no-gps': NoGpsIllustration, + 'no-images': NoImagesIllustration, + 'no-items-cart': NoItemsCartIllustration, + 'no-messages': NoMessagesIllustration, + 'no-search-result': NoSearchResultIllustration, + 'no-tasks': NoTasksIllustration, + site: SiteIllustration, +}); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-connection.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-connection.tsx new file mode 100644 index 000000000..95425fc0a --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-connection.tsx @@ -0,0 +1,54 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoConnectionIllustration: FC> = (props) => ( + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-credit-card.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-credit-card.tsx new file mode 100644 index 000000000..880a5e4cf --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-credit-card.tsx @@ -0,0 +1,48 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoCreditCardIllustration: FC> = (props) => ( + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-device.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-device.tsx new file mode 100644 index 000000000..7818f48e7 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-device.tsx @@ -0,0 +1,61 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoDeviceIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-documents.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-documents.tsx new file mode 100644 index 000000000..0d9035fba --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-documents.tsx @@ -0,0 +1,52 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoDocumentsIllustration: FC> = (props) => ( + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-gps.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-gps.tsx new file mode 100644 index 000000000..53bb43882 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-gps.tsx @@ -0,0 +1,57 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoGpsIllustration: FC> = (props) => ( + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-images.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-images.tsx new file mode 100644 index 000000000..63fb8223a --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-images.tsx @@ -0,0 +1,82 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoImagesIllustration: FC> = (props) => ( + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-items-cart.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-items-cart.tsx new file mode 100644 index 000000000..12e43c3c4 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-items-cart.tsx @@ -0,0 +1,86 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoItemsCartIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-messages.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-messages.tsx new file mode 100644 index 000000000..6343bc04b --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-messages.tsx @@ -0,0 +1,51 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoMessagesIllustration: FC> = (props) => ( + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-search-result.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-search-result.tsx new file mode 100644 index 000000000..1391358f2 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-search-result.tsx @@ -0,0 +1,76 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoSearchResultIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/no-tasks.tsx b/packages/design-system/src/components/ds-illustration/illustrations/no-tasks.tsx new file mode 100644 index 000000000..8727d6aab --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/no-tasks.tsx @@ -0,0 +1,61 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const NoTasksIllustration: FC> = (props) => ( + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/illustrations/site.tsx b/packages/design-system/src/components/ds-illustration/illustrations/site.tsx new file mode 100644 index 000000000..66af78ec9 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/illustrations/site.tsx @@ -0,0 +1,80 @@ +import type { FC, SVGProps } from 'react'; +import { IllustrationClouds } from './illustration-clouds'; + +export const SiteIllustration: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/design-system/src/components/ds-illustration/index.ts b/packages/design-system/src/components/ds-illustration/index.ts new file mode 100644 index 000000000..bab0559d0 --- /dev/null +++ b/packages/design-system/src/components/ds-illustration/index.ts @@ -0,0 +1,3 @@ +export { default as DsIllustration } from './ds-illustration'; +export type { DsIllustrationProps, DsIllustrationVariant } from './ds-illustration.types'; +export { dsIllustrationVariants } from './ds-illustration.types'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index 0dec0de78..2c621f0a0 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -31,6 +31,7 @@ export * from './components/ds-filter-status-icon'; export * from './components/ds-form-control'; export * from './components/ds-grid'; export * from './components/ds-icon'; +export * from './components/ds-illustration'; export * from './components/ds-key-value-pair'; export * from './components/ds-loader'; export * from './components/ds-main-menu'; diff --git a/packages/design-system/tests/storybook/docs-snippets.docs.test.ts b/packages/design-system/tests/storybook/docs-snippets.docs.test.ts index 4ae704ded..0199ebe12 100644 --- a/packages/design-system/tests/storybook/docs-snippets.docs.test.ts +++ b/packages/design-system/tests/storybook/docs-snippets.docs.test.ts @@ -34,6 +34,7 @@ const COMPONENTS = [ 'form-control', 'grid', 'icon', + 'illustration', 'key-value-pair', 'loader', 'main-menu',