From 6f4f4b0a0ab6312f31b932526130f802b73e27d8 Mon Sep 17 00:00:00 2001 From: Micky Date: Tue, 14 Apr 2026 14:22:57 +0200 Subject: [PATCH 1/7] refactor(design-system): remove DsDialog and DsConfirmation [AR-53409] (#344) --- .changeset/ripe-ravens-sleep.md | 7 + .../ds-confirmation.stories.module.scss | 31 --- .../ds-confirmation.stories.tsx | 257 ------------------ .../ds-confirmation/ds-confirmation.tsx | 17 -- .../ds-confirmation/ds-confirmation.types.ts | 29 -- .../src/components/ds-confirmation/index.ts | 2 - .../ds-dialog/ds-dialog.module.scss | 50 ---- .../ds-dialog/ds-dialog.stories.module.scss | 15 - .../ds-dialog/ds-dialog.stories.tsx | 125 --------- .../src/components/ds-dialog/ds-dialog.tsx | 70 ----- .../components/ds-dialog/ds-dialog.types.ts | 57 ---- .../src/components/ds-dialog/index.ts | 2 - packages/design-system/src/index.ts | 2 - packages/design-system/vitest.config.ts | 1 - .../src/__tests__/no-deprecated.test.ts | 118 -------- packages/eslint-plugin/src/index.ts | 36 --- 16 files changed, 7 insertions(+), 812 deletions(-) create mode 100644 .changeset/ripe-ravens-sleep.md delete mode 100644 packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.module.scss delete mode 100644 packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.tsx delete mode 100644 packages/design-system/src/components/ds-confirmation/ds-confirmation.tsx delete mode 100644 packages/design-system/src/components/ds-confirmation/ds-confirmation.types.ts delete mode 100644 packages/design-system/src/components/ds-confirmation/index.ts delete mode 100644 packages/design-system/src/components/ds-dialog/ds-dialog.module.scss delete mode 100644 packages/design-system/src/components/ds-dialog/ds-dialog.stories.module.scss delete mode 100644 packages/design-system/src/components/ds-dialog/ds-dialog.stories.tsx delete mode 100644 packages/design-system/src/components/ds-dialog/ds-dialog.tsx delete mode 100644 packages/design-system/src/components/ds-dialog/ds-dialog.types.ts delete mode 100644 packages/design-system/src/components/ds-dialog/index.ts diff --git a/.changeset/ripe-ravens-sleep.md b/.changeset/ripe-ravens-sleep.md new file mode 100644 index 000000000..522c78ef0 --- /dev/null +++ b/.changeset/ripe-ravens-sleep.md @@ -0,0 +1,7 @@ +--- +'@drivenets/design-system': major +'@drivenets/eslint-plugin-design-system': major +--- + +Removed deprecated components `DsDialog` and `DsConfirm` +Removed deprecation eslint rules for `DsDialog` and `DsConfirm` diff --git a/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.module.scss b/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.module.scss deleted file mode 100644 index ca51df58e..000000000 --- a/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -.customBodyContainer { - display: flex; - flex-direction: column; - gap: 16px; -} - -.customBodyText { - margin: 0; - color: var(--font-secondary); -} - -.radioGroup { - display: flex; - flex-direction: column; - gap: 8px; -} - -.radioLabel { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; -} - -.infoNote { - background: var(--background-secondary); - padding: 12px; - border-radius: 6px; - font-size: 14px; - color: var(--font-secondary); -} diff --git a/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.tsx b/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.tsx deleted file mode 100644 index 8d9ac6256..000000000 --- a/packages/design-system/src/components/ds-confirmation/ds-confirmation.stories.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { useState } from 'react'; -import { DsConfirmation } from './ds-confirmation'; -import { DsButton } from '../ds-button'; -import styles from './ds-confirmation.stories.module.scss'; - -/** - * @deprecated This component is deprecated. Use DsModal instead. - * @see {@link ../ds-modal/ds-modal.stories} for examples of the replacement component. - */ -const meta: Meta = { - title: 'Components/Confirmation (Deprecated)', - component: DsConfirmation, - tags: ['deprecated'], - parameters: { - layout: 'centered', - }, - argTypes: { - open: { - control: { type: 'boolean' }, - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: function Render() { - const [open, setOpen] = useState(false); - return ( - <> - setOpen(true)}>Open Confirmation - - - Confirm Action - - - Are you sure you want to proceed with this action? - - - { - console.log('Reject clicked'); - setOpen(false); - }} - > - No - - { - console.log('Accept clicked'); - setOpen(false); - }} - > - Yes - - - - - - ); - }, -}; - -export const WithCancel: Story = { - render: function Render() { - const [open, setOpen] = useState(false); - return ( - <> - setOpen(true)}>Open Confirmation - - - Save Changes - - - Do you want to save your changes before closing? - - { - console.log('Cancel clicked'); - setOpen(false); - }} - > - Cancel - - - { - console.log('Discard clicked'); - setOpen(false); - }} - > - Discard - - { - console.log('Save clicked'); - setOpen(false); - }} - > - Save - - - - - - ); - }, -}; - -export const Danger: Story = { - render: function Render() { - const [open, setOpen] = useState(false); - return ( - <> - setOpen(true)}>Open Confirmation - - - Delete Item - - - - Are you sure you want to delete this item? This action cannot be undone. - - - - { - console.log('Cancel clicked'); - setOpen(false); - }} - > - Cancel - - { - console.log('Delete clicked'); - setOpen(false); - }} - > - Delete - - - - - - ); - }, -}; - -export const CustomBody: Story = { - render: function Render() { - const [open, setOpen] = useState(false); - const [selectedOption, setSelectedOption] = useState('option1'); - - return ( - <> - setOpen(true)}>Open Custom Confirmation - - - Advanced Configuration - - - -
-

Please select your preferred configuration option:

-
- - - -
-
- Note: This action will apply the selected configuration to your current - project. You can change this setting later in the project settings. -
-
-
- - - { - console.log('Cancel clicked'); - setOpen(false); - }} - > - Cancel - - { - console.log('Apply configuration:', selectedOption); - setOpen(false); - }} - > - Apply Configuration - - - -
- - ); - }, -}; diff --git a/packages/design-system/src/components/ds-confirmation/ds-confirmation.tsx b/packages/design-system/src/components/ds-confirmation/ds-confirmation.tsx deleted file mode 100644 index ddf72a68b..000000000 --- a/packages/design-system/src/components/ds-confirmation/ds-confirmation.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { DsModal } from '../ds-modal'; -import type { DsConfirmationProps } from './ds-confirmation.types'; - -/** - * @deprecated DsConfirmation is deprecated. Use DsModal instead. - * @see {@link ../ds-modal} for the replacement component. - */ -export const DsConfirmation = (props: DsConfirmationProps) => ( - -); - -DsConfirmation.Header = DsModal.Header; -DsConfirmation.Title = DsModal.Title; -DsConfirmation.CloseTrigger = DsModal.CloseTrigger; -DsConfirmation.Body = DsModal.Body; -DsConfirmation.Footer = DsModal.Footer; -DsConfirmation.Actions = DsModal.Actions; diff --git a/packages/design-system/src/components/ds-confirmation/ds-confirmation.types.ts b/packages/design-system/src/components/ds-confirmation/ds-confirmation.types.ts deleted file mode 100644 index a5e97cd91..000000000 --- a/packages/design-system/src/components/ds-confirmation/ds-confirmation.types.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { CSSProperties, ReactNode } from 'react'; - -/** - * @deprecated DsConfirmationProps is deprecated. Use DsModal with variant="info" instead. - * @see {@link ../ds-modal/ds-modal} for the replacement. - */ -export interface DsConfirmationProps { - /** - * Whether the modal is open - */ - open: boolean; - /** - * Callback when modal open state changes - * @param open - whether the modal is open - */ - onOpenChange: (open: boolean) => void; - /** - * Additional CSS styles - */ - style?: CSSProperties; - /** - * Additional CSS class name - */ - className?: string; - /** - * Modal body content - */ - children: ReactNode; -} diff --git a/packages/design-system/src/components/ds-confirmation/index.ts b/packages/design-system/src/components/ds-confirmation/index.ts deleted file mode 100644 index 1c2e2a0ac..000000000 --- a/packages/design-system/src/components/ds-confirmation/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { DsConfirmation } from './ds-confirmation'; -export type { DsConfirmationProps } from './ds-confirmation.types'; diff --git a/packages/design-system/src/components/ds-dialog/ds-dialog.module.scss b/packages/design-system/src/components/ds-dialog/ds-dialog.module.scss deleted file mode 100644 index 811e3ae3f..000000000 --- a/packages/design-system/src/components/ds-dialog/ds-dialog.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -@use '../../styles/typography'; -@use '../../styles/mixins/bounding-box'; - -.overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.32); - z-index: 1199; -} - -.dialog { - @include bounding-box.bounding-box-vars; - - --_shadow-size: 18px; - --_shadow-y-offset: 6px; - - background: var(--color-dap-gray-050, #fff); - border-radius: 8px; - box-shadow: 0 var(--_shadow-y-offset) var(--_shadow-size) 0 rgba(4, 69, 204, 0.3); - padding: 12px; - min-width: 320px; - min-height: 120px; - z-index: 1200; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - max-width: 90vw; - max-height: 90vh; - display: flex; - flex-direction: column; -} - -.title { - @include typography.p-base; - @include typography.medium; - color: var(--color-dap-gray-700); -} - -.description { - @include typography.p-base; - color: var(--color-dap-gray-500); - margin-bottom: 16px; -} - -.customPlacement { - transform: none !important; - max-width: none; - max-height: none; -} diff --git a/packages/design-system/src/components/ds-dialog/ds-dialog.stories.module.scss b/packages/design-system/src/components/ds-dialog/ds-dialog.stories.module.scss deleted file mode 100644 index c823ab33c..000000000 --- a/packages/design-system/src/components/ds-dialog/ds-dialog.stories.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use '../../styles/typography'; - -.dialogContent { - padding: 20px; - min-width: 300px; -} - -.menuIcon { - cursor: pointer; - position: fixed; - top: 20px; - left: 20px; - z-index: 1; - color: var(--color-dap-blue-600); -} diff --git a/packages/design-system/src/components/ds-dialog/ds-dialog.stories.tsx b/packages/design-system/src/components/ds-dialog/ds-dialog.stories.tsx deleted file mode 100644 index 70c4917cf..000000000 --- a/packages/design-system/src/components/ds-dialog/ds-dialog.stories.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; -import { DsIcon } from '../ds-icon'; -import { DsButton } from '../ds-button'; -import { DsDialog } from '../ds-dialog'; -import styles from './ds-dialog.stories.module.scss'; - -const meta: Meta = { - title: 'Components/Dialog', - component: DsDialog, - parameters: { - layout: 'centered', - }, - argTypes: { - open: { - control: 'boolean', - description: 'Controls whether the dialog is open', - }, - onOpenChange: { - action: 'onOpenChange', - description: 'Function called when dialog open state changes', - }, - title: { - control: 'text', - description: 'Title of the dialog', - }, - description: { - control: 'text', - description: 'Description text for the dialog', - }, - hideTitle: { - control: 'boolean', - description: 'Whether to hide the title visually', - }, - hideDescription: { - control: 'boolean', - description: 'Whether to hide the description visually', - }, - modal: { - control: 'boolean', - description: 'Whether the dialog should be modal', - }, - customPosition: { - control: 'object', - description: - 'Custom position for the dialog in pixels relative to the viewport. Expects {top: number, left: number}', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Centered: Story = { - render: function Render(args) { - const [open, setOpen] = React.useState(false); - return ( - <> - setOpen(true)}>Open Dialog - -
This is a centered dialog example
-
- - ); - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const trigger = canvas.getByText('Open Dialog'); - await userEvent.click(trigger); - // Verify dialog is opened - await waitFor(() => { - return expect(screen.getByText(/Centered Dialog/)).toBeTruthy(); - }); - // Close dialog with Escape key - await userEvent.keyboard('{Escape}'); - // Verify dialog is closed - await waitFor(() => { - return expect(screen.queryByText(/Centered Dialog/)).toBeNull(); - }); - }, -}; - -export const CustomPosition: Story = { - render: function Render(args) { - const [open, setOpen] = React.useState(false); - - return ( - <> - setOpen(true)} /> - -
This is a custom positioned dialog example
-
- - ); - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const trigger = canvas.getByText(/menu/i); - await userEvent.click(trigger); - // Verify dialog is opened - await waitFor(() => { - return expect(screen.getByText(/Custom Position Dialog/)).toBeTruthy(); - }); - // Close dialog with Escape key - await userEvent.keyboard('{Escape}'); - // Verify dialog is closed - await waitFor(() => { - return expect(screen.queryByText(/Custom Position Dialog/)).toBeNull(); - }); - }, -}; diff --git a/packages/design-system/src/components/ds-dialog/ds-dialog.tsx b/packages/design-system/src/components/ds-dialog/ds-dialog.tsx deleted file mode 100644 index 05fd477fc..000000000 --- a/packages/design-system/src/components/ds-dialog/ds-dialog.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type React from 'react'; -import * as RadixDialog from '@radix-ui/react-dialog'; -import { Root as VisuallyHidden } from '@radix-ui/react-visually-hidden'; -import classNames from 'classnames'; -import styles from './ds-dialog.module.scss'; -import type { DsDialogProps } from './ds-dialog.types'; - -const DsDialog: React.FC = ({ - open, - onOpenChange, - title, - hideTitle, - description, - hideDescription, - children, - className, - anchorRef, - customPosition, - modal = true, -}) => { - let style: React.CSSProperties = {}; - if (customPosition) { - style = { position: 'fixed', ...customPosition }; - } else if (anchorRef?.current) { - const rect = anchorRef.current.getBoundingClientRect(); - style = { position: 'fixed', top: rect.bottom, left: rect.left }; - } - - return ( - - - {modal && } - - - {hideTitle ? ( - - {title} - - ) : ( - {title} - )} - - {description && ( - - {hideDescription ? ( - - - {description} - - - ) : ( - - {description} - - )} - - )} - {children} - - - - ); -}; - -export default DsDialog; diff --git a/packages/design-system/src/components/ds-dialog/ds-dialog.types.ts b/packages/design-system/src/components/ds-dialog/ds-dialog.types.ts deleted file mode 100644 index 9a42a6313..000000000 --- a/packages/design-system/src/components/ds-dialog/ds-dialog.types.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type React from 'react'; - -export interface DsDialogProps { - /** - * Controls whether the dialog is open. - */ - open: boolean; - /** - * Callback fired when the open state should change. - */ - onOpenChange?: (open: boolean) => void; - /** - * The accessible title for the dialog. Required for accessibility. - */ - title: string; - /** - * If true, the dialog title will be visually hidden but accessible to screen readers. - */ - hideTitle?: boolean; - /** - * The accessible description for the dialog. Optional, but recommended for accessibility. - */ - description?: string; - /** - * If true, the dialog description will be visually hidden but accessible to screen readers. - */ - hideDescription?: boolean; - /** - * The content to render inside the dialog. - */ - children: React.ReactNode; - /** - * Additional CSS class names for the dialog container. - */ - className?: string; - /** - * Ref to the element the dialog should be anchored to (for relative placement) - */ - anchorRef?: React.RefObject; - /** - * Custom fixed position (e.g., { top: number, left: number }) - */ - customPosition?: { - /** - * Distance in pixels from the top of the viewport. - */ - top: number; - /** - * Distance in pixels from the left of the viewport. - */ - left: number; - }; - /** - * If true, show modal overlay and center dialog (default: true) - */ - modal?: boolean; -} diff --git a/packages/design-system/src/components/ds-dialog/index.ts b/packages/design-system/src/components/ds-dialog/index.ts deleted file mode 100644 index cfebd91ab..000000000 --- a/packages/design-system/src/components/ds-dialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as DsDialog } from './ds-dialog'; -export * from './ds-dialog.types'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index bc8e9893d..ee3629559 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -15,11 +15,9 @@ export * from './components/ds-comment-bubble'; export * from './components/ds-comment-card'; export * from './components/ds-comment-indicator'; export * from './components/ds-comments-drawer'; -export * from './components/ds-confirmation'; export * from './components/ds-date-input'; export * from './components/ds-date-picker'; export * from './components/ds-date-range-picker'; -export * from './components/ds-dialog'; export * from './components/ds-divider'; export * from './components/ds-drawer'; export * from './components/ds-dropdown-menu'; diff --git a/packages/design-system/vitest.config.ts b/packages/design-system/vitest.config.ts index 0e8cd96f0..d97ca94cb 100644 --- a/packages/design-system/vitest.config.ts +++ b/packages/design-system/vitest.config.ts @@ -23,7 +23,6 @@ export default defineConfig({ // deprecated components '**/ds-chip/**', '**/ds-chip-group/**', - '**/ds-confirmation/**', '**/ds-date-input/**', '**/ds-system-status/**', ], diff --git a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts index bd4dc926f..f245c60f3 100644 --- a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts +++ b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts @@ -53,124 +53,6 @@ ruleTester.run('no-deprecated-ds-button-empty-design', plugin.rules['no-deprecat ], }); -ruleTester.run('no-deprecated-ds-dialog', plugin.rules['no-deprecated-ds-dialog'], { - valid: ['', '', ''], - - invalid: [ - { - code: 'Click me', - errors: [ - { - message: `DsDialog is deprecated. Use DsModal or DsConfirmation instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 10, - }, - ], - }, - ], -}); - -ruleTester.run('no-deprecated-ds-confirmation', plugin.rules['no-deprecated-ds-confirmation'], { - valid: [''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsConfirmation is deprecated. Use DsModal instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 16, - }, - ], - }, - ], -}); - -ruleTester.run('no-deprecated-ds-status-badge', plugin.rules['no-deprecated-ds-status-badge'], { - valid: [''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsStatusBadge is deprecated. Use DsStatusBadgeV2 instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 15, - }, - ], - }, - ], -}); - -ruleTester.run('no-deprecated-ds-system-status', plugin.rules['no-deprecated-ds-system-status'], { - valid: [''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsSystemStatus is deprecated. Use DsStatusBadgeV2 instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 16, - }, - ], - }, - ], -}); - -ruleTester.run( - 'no-deprecated-ds-dropdown-menu-legacy', - plugin.rules['no-deprecated-ds-dropdown-menu-legacy'], - { - valid: [''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsDropdownMenuLegacy is deprecated. Use DsDropdownMenu instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 22, - }, - ], - }, - ], - }, -); - -ruleTester.run('no-deprecated-ds-radio-group-legacy', plugin.rules['no-deprecated-ds-radio-group-legacy'], { - valid: [''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsRadioGroupLegacy is deprecated. Use DsRadioGroup instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 20, - }, - ], - }, - ], -}); - ruleTester.run('no-deprecated-ds-chip', plugin.rules['no-deprecated-ds-chip'], { valid: ['', ''], diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index a11469fed..05c3eb5a6 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -15,42 +15,6 @@ const eslintPlugin = createPlugin( message: `Omitting the design attribute for DsButton is not allowed. Pass 'design="v1.2"' so the new design is used.`, }, - { - name: 'no-deprecated-ds-dialog', - selector: JSXElementName('DsDialog'), - message: `DsDialog is deprecated. Use DsModal or DsConfirmation instead.`, - }, - - { - name: 'no-deprecated-ds-confirmation', - selector: JSXElementName('DsConfirmation'), - message: `DsConfirmation is deprecated. Use DsModal instead.`, - }, - - { - name: 'no-deprecated-ds-status-badge', - selector: JSXElementName('DsStatusBadge'), - message: `DsStatusBadge is deprecated. Use DsStatusBadgeV2 instead.`, - }, - - { - name: 'no-deprecated-ds-system-status', - selector: JSXElementName('DsSystemStatus'), - message: `DsSystemStatus is deprecated. Use DsStatusBadgeV2 instead.`, - }, - - { - name: 'no-deprecated-ds-dropdown-menu-legacy', - selector: JSXElementName('DsDropdownMenuLegacy'), - message: `DsDropdownMenuLegacy is deprecated. Use DsDropdownMenu instead.`, - }, - - { - name: 'no-deprecated-ds-radio-group-legacy', - selector: JSXElementName('DsRadioGroupLegacy'), - message: `DsRadioGroupLegacy is deprecated. Use DsRadioGroup instead.`, - }, - { name: 'no-deprecated-ds-chip', selector: JSXElementName('DsChip'), From f27e584e598c57da018b365cd5601eccb73f6449 Mon Sep 17 00:00:00 2001 From: Micky Date: Wed, 15 Apr 2026 09:52:55 +0200 Subject: [PATCH 2/7] refactor(design-system)!: remove deprecated DsDropdownMenuLegacy and DsRadioGroupLegacy [AR-53409] (#340) --- .changeset/fluffy-clubs-greet.md | 7 + packages/design-system/package.json | 3 +- .../ds-dropdown-menu-legacy.stories.tsx | 99 ------------ .../ds-dropdown-menu.module.scss | 18 --- .../ds-dropdown-menu/ds-dropdown-menu.tsx | 92 ------------ .../ds-dropdown-menu.types.ts | 89 +---------- .../src/components/ds-dropdown-menu/index.ts | 2 +- .../ds-radio-group/ds-radio-group.tsx | 58 +------ .../ds-radio-group/ds-radio-group.types.ts | 61 -------- .../src/components/ds-radio-group/index.ts | 2 +- pnpm-lock.yaml | 142 +----------------- 11 files changed, 13 insertions(+), 560 deletions(-) create mode 100644 .changeset/fluffy-clubs-greet.md delete mode 100644 packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu-legacy.stories.tsx diff --git a/.changeset/fluffy-clubs-greet.md b/.changeset/fluffy-clubs-greet.md new file mode 100644 index 000000000..9fe45666d --- /dev/null +++ b/.changeset/fluffy-clubs-greet.md @@ -0,0 +1,7 @@ +--- +'@drivenets/design-system': major +'@drivenets/eslint-plugin-design-system': major +--- + +Remove deprecated components `DsDropdownMenuLegacy` and `DsRadioGroupLegacy` +Remove deprecation eslint rules for `DsDropdownMenuLegacy` and `DsRadioGroupLegacy` diff --git a/packages/design-system/package.json b/packages/design-system/package.json index dd2a12047..069529c63 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -61,9 +61,8 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@radix-ui/react-collapsible": "^1.1.13", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.16", - "@radix-ui/react-dropdown-menu": "^2.1.17", "@radix-ui/react-tooltip": "^1.2.9", "@radix-ui/react-visually-hidden": "^1.2.5", "@tanstack/react-table": "^8.21.3", diff --git a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu-legacy.stories.tsx b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu-legacy.stories.tsx deleted file mode 100644 index 22e690805..000000000 --- a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu-legacy.stories.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { expect, screen, userEvent, within } from 'storybook/test'; -import { DsDropdownMenuLegacy } from './ds-dropdown-menu'; -import './ds-dropdown-menu.stories.scss'; -import { DsIcon } from '../ds-icon'; - -const meta: Meta = { - title: 'Components/DropdownMenuLegacy (Deprecated)', - component: DsDropdownMenuLegacy, - parameters: { - layout: 'centered', - }, - tags: ['deprecated'], - argTypes: { - children: { - control: 'text', - description: 'Content to display inside the component', - }, - contentGap: { - control: 'number', - description: 'The gap between the trigger and dropdown content in pixels', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - parameters: { - docs: { - description: { - story: - 'Dropdown menu with a custom trigger styled to match the design. The menu items can include icons and can be disabled.', - }, - }, - }, - args: { - options: [ - { label: 'Edit', icon: 'edit', onClick: () => console.log('Edit clicked') }, - { label: 'Delete', icon: 'delete', onClick: () => console.log('Delete clicked') }, - { label: 'Share', icon: 'share', onClick: () => console.log('Share clicked') }, - { - label: 'Disabled Option', - icon: 'block', - disabled: true, - onClick: () => console.log('Disabled clicked'), - }, - ], - contentGap: 4, - }, - render: function Render(args) { - return ( - -
- Actions - -
-
- ); - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - // Check initial state - await expect(canvas.getByText('Actions')).toBeInTheDocument(); - - // Open dropdown menu - await userEvent.click(canvas.getByText('Actions')); - - // Check all menu items are present - await expect(screen.getByRole('menuitem', { name: /Edit/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Delete/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Share/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Disabled Option/ })).toBeInTheDocument(); - - // Check disabled state - const disabledOption = screen.getByRole('menuitem', { name: /Disabled Option/ }); - await expect(disabledOption).toHaveAttribute('aria-disabled', 'true'); - - // Click an option - await userEvent.click(screen.getByRole('menuitem', { name: /Edit/ })); - - // Close dropdown with Escape key - await userEvent.keyboard('{Escape}'); - - // Open dropdown again - await userEvent.click(canvas.getByText('Actions')); - - // Check all options are shown again - await expect(screen.getByRole('menuitem', { name: /Edit/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Delete/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Share/ })).toBeInTheDocument(); - await expect(screen.getByRole('menuitem', { name: /Disabled Option/ })).toBeInTheDocument(); - - // Close dropdown with Escape key - await userEvent.keyboard('{Escape}'); - }, -}; diff --git a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss index e7d39ceda..9ec3f5377 100644 --- a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +++ b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss @@ -1,4 +1,3 @@ -@use '../../styles/typography'; @use '../../styles/shared/dropdown'; .content { @@ -124,20 +123,3 @@ .triggerItemIcon { margin-left: auto; } - -/** Legacy classes */ -.contentLegacy { - @include dropdown.dropdown-content; - min-width: 200px; - width: var(--radix-select-trigger-width); -} - -.viewportLegacy { - @include dropdown.dropdown-viewport; -} - -.itemLegacy { - @include dropdown.dropdown-item; - @include typography.p-special; - @include typography.ellipsis; -} diff --git a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx index 54329e581..8653e6568 100644 --- a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +++ b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx @@ -1,13 +1,11 @@ import type React from 'react'; import { createContext, Fragment, useContext, useState } from 'react'; -import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { Menu } from '@ark-ui/react/menu'; import { Portal } from '@ark-ui/react/portal'; import classNames from 'classnames'; import styles from './ds-dropdown-menu.module.scss'; import { DsIcon } from '../ds-icon'; import { DsTypography } from '../ds-typography'; -import { DsTextInput } from '../ds-text-input'; import type { DsDropdownMenuActionsProps, DsDropdownMenuContentProps, @@ -17,7 +15,6 @@ import type { DsDropdownMenuItemGroupProps, DsDropdownMenuItemIndicatorProps, DsDropdownMenuItemProps, - DsDropdownMenuLegacyProps, DsDropdownMenuPositioning, DsDropdownMenuRootProps, DsDropdownMenuSeparatorProps, @@ -281,95 +278,6 @@ const TriggerItem: React.FC = ({ className, styl ); }; -/** - * DEPRECATED: Legacy DsDropdownMenu component with options array - * Use compound component pattern instead - * @deprecated - */ -/* c8 ignore start */ -export const DsDropdownMenuLegacy: React.FC = ({ - options, - children, - contentGap = 0, - className, - style, - align = 'center', - side = 'bottom', - disablePortal = false, - disableSearch = true, - selected, - onSelect, -}) => { - const [open, setOpen] = useState(false); - const [searchTerm, setSearchTerm] = useState(''); - - const Wrapper = disablePortal ? Fragment : DropdownMenu.Portal; - - const filteredOptions = disableSearch - ? options - : options.filter((option) => option.label.toLowerCase().includes(searchTerm.toLowerCase())); - - return ( - - {children} - - - {!disableSearch && ( - , - }} - onKeyDown={(e) => e.stopPropagation()} - /> - )} - {filteredOptions.map((option, i) => ( - { - e.stopPropagation(); - - if (!option.disabled) { - if (option.value) { - onSelect?.(option.value); - } - option.onClick?.(e); - setOpen(false); - } - }} - > - {option.icon && } - {option.label} - {option.value && selected === option.value && ( - - - - )} - - ))} - - - - ); -}; -/* c8 ignore stop */ - /** * Design system DsDropdownMenu component * diff --git a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.types.ts b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.types.ts index 5d2142037..ffa3786a8 100644 --- a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.types.ts +++ b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.types.ts @@ -1,6 +1,5 @@ -import type { CSSProperties, MouseEvent, ReactNode } from 'react'; +import type { CSSProperties, ReactNode } from 'react'; import type { Menu } from '@ark-ui/react/menu'; -import type { IconType } from '../ds-icon'; export const dropdownMenuItemVariants = ['default', 'error'] as const; export type DropdownMenuItemVariant = (typeof dropdownMenuItemVariants)[number]; @@ -18,92 +17,6 @@ export type DsDropdownMenuPositioning = Pick< 'placement' | 'gutter' | 'sameWidth' | 'getAnchorRect' >; -/** - * DEPRECATED: Legacy dropdown menu option configuration - * Use compound component pattern instead - * @deprecated - */ -export interface DsDropdownMenuOptionLegacy { - /** - * Display label for the menu option - */ - label: string; - /** - * Optional icon to display - */ - icon?: IconType; - /** - * Whether this option is disabled - */ - disabled?: boolean; - /** - * Click handler for the option - */ - onClick?: (e: MouseEvent) => void; - /** - * Optional value for selection tracking - */ - value?: string; -} - -/** - * DEPRECATED: Legacy props for DsDropdownMenuLegacy component - * Use compound component pattern instead - * @deprecated - */ -export interface DsDropdownMenuLegacyProps { - /** - * The options to be displayed in the dropdown menu - */ - options: DsDropdownMenuOptionLegacy[]; - /** - * Optional children to be rendered inside the component - * Typically used for the trigger element - */ - children?: ReactNode | undefined; - /** - * The gap between the trigger and dropdown content in pixels - * @default 0 - */ - contentGap?: number; - /** - * Additional CSS class names - */ - className?: string; - /** - * Optional inline styles to apply to the component - */ - style?: CSSProperties; - /** - * The alignment of the dropdown content - * @default 'center' - */ - align?: 'start' | 'center' | 'end'; - /** - * The side of the dropdown content - * @default 'bottom' - */ - side?: 'top' | 'right' | 'bottom' | 'left'; - /** - * Whether to render in place instead of using portals - * @default false - */ - disablePortal?: boolean; - /** - * Whether to disable the search functionality - * @default false - */ - disableSearch?: boolean; - /** - * Currently selected value (for selection tracking) - */ - selected?: string; - /** - * Callback when an option with a value is selected - */ - onSelect?: (value: string) => void; -} - /** * Props for the DsDropdownMenu Root component */ diff --git a/packages/design-system/src/components/ds-dropdown-menu/index.ts b/packages/design-system/src/components/ds-dropdown-menu/index.ts index 3a6c15bb8..c12c6088a 100644 --- a/packages/design-system/src/components/ds-dropdown-menu/index.ts +++ b/packages/design-system/src/components/ds-dropdown-menu/index.ts @@ -1,2 +1,2 @@ -export { DsDropdownMenu, DsDropdownMenuLegacy } from './ds-dropdown-menu'; +export { DsDropdownMenu } from './ds-dropdown-menu'; export * from './ds-dropdown-menu.types'; diff --git a/packages/design-system/src/components/ds-radio-group/ds-radio-group.tsx b/packages/design-system/src/components/ds-radio-group/ds-radio-group.tsx index 85e199122..0658bfeee 100644 --- a/packages/design-system/src/components/ds-radio-group/ds-radio-group.tsx +++ b/packages/design-system/src/components/ds-radio-group/ds-radio-group.tsx @@ -3,11 +3,7 @@ import { RadioGroup } from '@ark-ui/react/radio-group'; import classNames from 'classnames'; import { DsTypography } from '../ds-typography'; import styles from './ds-radio-group.module.scss'; -import type { - DsRadioGroupItemProps, - DsRadioGroupLegacyProps, - DsRadioGroupRootProps, -} from './ds-radio-group.types'; +import type { DsRadioGroupItemProps, DsRadioGroupRootProps } from './ds-radio-group.types'; /** * Root component - provides radio group context @@ -57,58 +53,6 @@ const Item: React.FC = ({ ); }; -/** - * DEPRECATED: Legacy DsRadioGroup component with options array - * Use compound component pattern instead: DsRadioGroup.Root + DsRadioGroup.Item - * @deprecated - */ -/* c8 ignore start */ -export const DsRadioGroupLegacy: React.FC = ({ - options, - value, - defaultValue, - onValueChange, - disabled, - className, - style, -}) => ( - { - if (details.value !== null) { - onValueChange?.(details.value); - } - }} - > - {options.map((option) => ( - - -
-
-
- - - {option.label} - {!!option.labelInfo && ( - - {option.labelInfo} - - )} - - ))} - -); -/* c8 ignore stop */ - /** * Design system RadioGroup component * diff --git a/packages/design-system/src/components/ds-radio-group/ds-radio-group.types.ts b/packages/design-system/src/components/ds-radio-group/ds-radio-group.types.ts index 26473524a..55c1b1cdd 100644 --- a/packages/design-system/src/components/ds-radio-group/ds-radio-group.types.ts +++ b/packages/design-system/src/components/ds-radio-group/ds-radio-group.types.ts @@ -1,4 +1,3 @@ -import type React from 'react'; import { type RadioGroupItemProps, type RadioGroupRootProps } from '@ark-ui/react/radio-group'; /** @@ -62,63 +61,3 @@ export interface DsRadioGroupItemProps extends Pick< */ labelInfo?: string; } - -/** - * DEPRECATED: Legacy radio option configuration - * Use compound component pattern instead: - * @deprecated - */ -export interface DsRadioOptionLegacy { - /** - * Display label for the radio option - */ - label: string; - /** - * Value associated with this radio option - */ - value: ValueType; - /** - * Additional label info for the radio option - */ - labelInfo?: string; - /** - * Whether this radio option is disabled - */ - disabled?: boolean; -} - -/** - * DEPRECATED: Legacy props for DsRadioGroupLegacy component - * Use compound component pattern instead - * @deprecated - */ -export interface DsRadioGroupLegacyProps { - /** - * The radio group options - */ - options: TOption[]; - /** - * The selected value - */ - value?: TOption['value']; - /** - * The default selected value - */ - defaultValue?: TOption['value']; - /** - * Event handler called when the selected value changes - */ - onValueChange?: (value: TOption['value']) => void; - /** - * Whether the entire radio group is disabled. - */ - disabled?: boolean; - /** - * Additional CSS class names - */ - className?: string; - /** - * Additional inline styles - */ - style?: React.CSSProperties; -} diff --git a/packages/design-system/src/components/ds-radio-group/index.ts b/packages/design-system/src/components/ds-radio-group/index.ts index e531d2179..44a3f31c4 100644 --- a/packages/design-system/src/components/ds-radio-group/index.ts +++ b/packages/design-system/src/components/ds-radio-group/index.ts @@ -1,2 +1,2 @@ -export { DsRadioGroup, DsRadioGroupLegacy } from './ds-radio-group'; +export { DsRadioGroup } from './ds-radio-group'; export * from './ds-radio-group.types'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39dfc5c15..e11454ba2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,14 +194,11 @@ importers: specifier: ^3.0.0 version: 3.12.2 '@radix-ui/react-collapsible': - specifier: ^1.1.13 + specifier: ^1.1.12 version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-dialog': specifier: ^1.1.16 version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.17 - version: 2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-tooltip': specifier: ^1.2.9 version: 1.2.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -2290,19 +2287,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.9': - resolution: {integrity: sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-compose-refs@1.1.3': resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} peerDependencies: @@ -2334,15 +2318,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-direction@1.1.2': - resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-dismissable-layer@1.1.12': resolution: {integrity: sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==} peerDependencies: @@ -2356,19 +2331,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.17': - resolution: {integrity: sha512-S6b3Jm57sY5EdDyOMLkacbB0qMnKhy1RCKZCt795ZkmtUOAvojYIZ5p7dXHIh5Cyr3jCLLI5/g64V3FKLudZmw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-focus-guards@1.1.4': resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} peerDependencies: @@ -2400,19 +2362,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-menu@2.1.17': - resolution: {integrity: sha512-fmbNnFyf+JYCN0DhhWnEdUTDnZD1mXaPQWivdsPIb8oOSbARfD3LIQJbLCG8a8QLCwoMxiJ7GVPIFcC8Dw8v2Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-popper@1.3.0': resolution: {integrity: sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==} peerDependencies: @@ -2465,19 +2414,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.12': - resolution: {integrity: sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-slot@1.2.5': resolution: {integrity: sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==} peerDependencies: @@ -8445,18 +8381,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collection@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 @@ -8491,12 +8415,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-direction@1.1.2(@types/react@19.2.15)(react@19.2.6)': - dependencies: - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - '@radix-ui/react-dismissable-layer@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 @@ -8510,21 +8428,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dropdown-menu@2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 @@ -8549,32 +8452,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-menu@2.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - aria-hidden: 1.2.6 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popper@1.3.0(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -8621,23 +8498,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-roving-focus@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-slot@1.2.5(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) From ef24e74ec7f38cff4369fc2050a4be56387ee2b2 Mon Sep 17 00:00:00 2001 From: Micky Date: Wed, 15 Apr 2026 14:19:53 +0200 Subject: [PATCH 3/7] refactor(design-system)!: remove deprecated DsSystemStatus component [AR-53409] (#339) --- .changeset/loose-stamps-study.md | 7 ++ .../ds-button-legacy.module.scss | 22 ++--- .../ds-drawer/ds-drawer.stories.tsx | 7 +- .../ds-system-status.module.scss | 87 ------------------- .../ds-system-status.stories.ts | 48 ---------- .../ds-system-status/ds-system-status.tsx | 33 ------- .../ds-system-status.types.ts | 37 -------- .../src/components/ds-system-status/index.ts | 10 --- .../progress-infographic.module.scss | 6 +- .../stories/ds-table.stories.module.scss | 8 +- packages/design-system/src/index.ts | 1 - .../design-system/src/styles/_colors.scss | 7 -- packages/design-system/vitest.config.ts | 1 - 13 files changed, 28 insertions(+), 246 deletions(-) create mode 100644 .changeset/loose-stamps-study.md delete mode 100644 packages/design-system/src/components/ds-system-status/ds-system-status.module.scss delete mode 100644 packages/design-system/src/components/ds-system-status/ds-system-status.stories.ts delete mode 100644 packages/design-system/src/components/ds-system-status/ds-system-status.tsx delete mode 100644 packages/design-system/src/components/ds-system-status/ds-system-status.types.ts delete mode 100644 packages/design-system/src/components/ds-system-status/index.ts diff --git a/.changeset/loose-stamps-study.md b/.changeset/loose-stamps-study.md new file mode 100644 index 000000000..28b797bb5 --- /dev/null +++ b/.changeset/loose-stamps-study.md @@ -0,0 +1,7 @@ +--- +'@drivenets/design-system': major +'@drivenets/eslint-plugin-design-system': major +--- + +Remove deprecated component `DsSystemStatus` +Remove deprecation eslint rules for `DsSystemStatus` diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss index 91656561d..65e84c617 100644 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +++ b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss @@ -1,21 +1,21 @@ @use '../../../../styles/typography'; -$color-primary-default: var(--color-dap-blue-600); -$color-primary-hover: var(--background-active-status); -$color-primary-active: var(--color-dap-purple-200); -$color-primary-disabled: var(--color-dap-gray-400); +$color-primary-default: var(--action-cta1); +$color-primary-hover: var(--action-hover-light); +$color-primary-active: var(--action-active-light); +$color-primary-disabled: var(--action-disabled); -$color-secondary-default: var(--color-dap-brand-300); +$color-secondary-default: var(--action-cta3); $color-secondary-hover: var(--color-dap-gray-500); -$color-secondary-disabled: var(--color-dap-gray-400); +$color-secondary-disabled: var(--action-disabled); -$color-error-default: var(--background-error); -$color-error-hover: var(--color-dap-red-100); -$color-error-active: var(--color-dap-red-100); -$color-error-disabled: var(--color-dap-gray-400); +$color-error-default: var(--background-background-negative); +$color-error-hover: var(--utility-error); +$color-error-active: var(--utility-error); +$color-error-disabled: var(--action-disabled); $color-white: var(--color-dap-gray-050); -$color-neutral-6: var(--color-dap-gray-200); +$color-neutral-6: var(--utility-disabled); $color-neutral-4: var(--color-dap-gray-500); @mixin button-filled($background, $hover, $active, $disabled) { diff --git a/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx b/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx index 7645d134f..5a65d6a15 100644 --- a/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx +++ b/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx @@ -6,8 +6,7 @@ import DsDrawer from './ds-drawer'; import { DsButton } from '../ds-button'; import { DsTextInput } from '../ds-text-input'; import { DsIcon } from '../ds-icon'; -// TODO: Use DsStatusBadge instead. -import { DsSystemStatus } from '../ds-system-status'; +import { DsStatusBadge } from '../ds-status-badge'; import styles from './ds-drawer.stories.module.scss'; import { DsTypography } from '../ds-typography'; import type { DsDrawerColumns, DsDrawerProps } from './ds-drawer.types'; @@ -86,7 +85,7 @@ export const Default: Story = { <> - Default Drawer + Default Drawer
-

Total filters: {filters.length}

-

- Selected filters: [ - {filters - .filter((filter) => filter.selected) - .map((filter) => `"${filter.label}"`) - .join(', ')} - ] -

-
-
- ); - }, -}; diff --git a/packages/design-system/src/components/ds-chip-group/ds-chip-group.tsx b/packages/design-system/src/components/ds-chip-group/ds-chip-group.tsx deleted file mode 100644 index f11738afb..000000000 --- a/packages/design-system/src/components/ds-chip-group/ds-chip-group.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import type React from 'react'; -import { useRef, useState } from 'react'; -import * as RadixDialog from '@radix-ui/react-dialog'; -import { Root as VisuallyHidden } from '@radix-ui/react-visually-hidden'; -import classNames from 'classnames'; -import styles from './ds-chip-group.module.scss'; -import type { DsChipGroupProps } from './ds-chip-group.types'; -import { useChipRowCalculation } from './hooks/use-chip-row-calculation'; -import { DsTypography } from '../ds-typography'; -import { DsChip } from '../ds-chip'; -import { DsCheckbox } from '../ds-checkbox'; -import { DsButton } from '../ds-button'; -import { DsIcon } from '../ds-icon'; - -/** - * @deprecated This component is deprecated. Use `DsTagFilter` instead. - * @see {@link ../ds-tag-filter} for the replacement component. - */ -const DsChipGroup: React.FC = ({ - items, - label = 'Filtered by:', - onClearAll, - onItemDelete, - onItemSelect, - className, - style, -}) => { - const [dialogOpen, setDialogOpen] = useState(false); - const showAllChipsRef = useRef(null); - const chipsWrapperRef = useRef(null); - const visibleCount = useChipRowCalculation({ - chipsWrapperRef, - totalFilters: items.length, - }); - - if (items.length === 0) { - return null; - } - - const hiddenCount = items.length - visibleCount; - const showExpandButton = hiddenCount > 0; - - const handleOpenDialog = () => { - setDialogOpen(true); - }; - - let dialogStyle: React.CSSProperties = {}; - if (showExpandButton && showAllChipsRef.current) { - const rect = showAllChipsRef.current.getBoundingClientRect(); - dialogStyle = { - position: 'fixed', - top: rect.bottom + 4, - left: rect.left, - }; - } - - return ( -
- {label && ( - - {label} - - )} - -
- {items.map((item, index) => ( - onItemSelect?.(item)} - onDelete={() => onItemDelete?.(item)} - className={classNames({ - [styles.hidden]: index >= visibleCount, - })} - /> - ))} - {showExpandButton && ( - - )} -
- - {onClearAll && ( - - - Clear all - - )} - - - - - - - -
- {items - .filter((item, index) => index >= visibleCount) - .map((item) => ( -
- onItemSelect?.(item)} - label={item.label} - /> -
- ))} -
-
-
-
-
- ); -}; - -export default DsChipGroup; diff --git a/packages/design-system/src/components/ds-chip-group/ds-chip-group.types.ts b/packages/design-system/src/components/ds-chip-group/ds-chip-group.types.ts deleted file mode 100644 index 6deec135d..000000000 --- a/packages/design-system/src/components/ds-chip-group/ds-chip-group.types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { CSSProperties } from 'react'; - -/** - * @deprecated This interface is deprecated. Use `DsTagFilter` types instead. - * @see {@link ../ds-tag-filter} for the replacement types. - */ -export interface ChipItem { - /** - * Unique identifier for the chip - */ - id: string; - /** - * The label text to display in the chip - */ - label: string; - /** - * Additional metadata to store with the chip - */ - metadata?: Record; - /** - * Whether the chip is selected/checked - */ - selected?: boolean; -} - -/** - * @deprecated This interface is deprecated. Use `DsTagFilterProps` from `ds-tag-filter` instead. - * @see {@link ../ds-tag-filter/ds-tag-filter.types} for the replacement interface. - */ -export interface DsChipGroupProps { - /** - * Array of chip items to display - */ - items: ChipItem[]; - /** - * Label text to display before the chips (e.g., "Filtered by:") - * @default "Filtered by:" - */ - label?: string; - /** - * Callback when "Clear all" is clicked - */ - onClearAll?: () => void; - /** - * Callback when a chip is deleted/unchecked - */ - onItemDelete?: (item: ChipItem) => void; - /** - * Callback when a chip is selected - */ - onItemSelect?: (item: ChipItem) => void; - /** - * Additional CSS class names - */ - className?: string; - /** - * Additional styles to apply to the component - */ - style?: CSSProperties; -} diff --git a/packages/design-system/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts b/packages/design-system/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts deleted file mode 100644 index d53c99c76..000000000 --- a/packages/design-system/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { type RefObject, useLayoutEffect, useState } from 'react'; - -interface UseChipRowCalculationProps { - chipsWrapperRef: RefObject; - totalFilters: number; -} - -/** - * Custom hook to calculate how many chips can fit in 2 rows - * Uses a simple approach: render all, measure positions, hide overflow - */ -export const useChipRowCalculation = ({ chipsWrapperRef, totalFilters }: UseChipRowCalculationProps) => { - const [visibleCount, setVisibleCount] = useState(totalFilters); - - useLayoutEffect(() => { - const calculateVisibleChips = () => { - if (!chipsWrapperRef.current) { - return; - } - - const wrapper = chipsWrapperRef.current; - const children = Array.from(wrapper.children) as HTMLElement[]; - - if (children.length === 0) { - return; - } - - const cs = getComputedStyle(wrapper); - const gap = parseFloat(cs.columnGap || cs.gap || '0') || 8; - let index; - let line = 0; - let total = 0; - - for (index = 0; index < children.length; index++) { - const current = children[index]; - - if (!current) { - continue; - } - - const offset = current.offsetWidth === wrapper.clientWidth ? 0 : gap; - const next = offset + current.offsetWidth; - - if (total + next >= wrapper.clientWidth) { - line++; - if (line >= 2) { - break; - } - total = next; - } else { - total += next; - } - } - - const result = Math.max(1, line < 2 ? index : index - 1); - setVisibleCount(result); - }; - - // Use requestAnimationFrame to ensure DOM is fully laid out - const rafId = requestAnimationFrame(() => { - calculateVisibleChips(); - }); - - const resizeObserver = new ResizeObserver(() => { - requestAnimationFrame(() => { - calculateVisibleChips(); - }); - }); - - if (chipsWrapperRef.current) { - resizeObserver.observe(chipsWrapperRef.current); - } - - return () => { - cancelAnimationFrame(rafId); - resizeObserver.disconnect(); - }; - }, [chipsWrapperRef, totalFilters]); - - return visibleCount; -}; diff --git a/packages/design-system/src/components/ds-chip-group/index.ts b/packages/design-system/src/components/ds-chip-group/index.ts deleted file mode 100644 index 133bd29d9..000000000 --- a/packages/design-system/src/components/ds-chip-group/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as DsChipGroup } from './ds-chip-group'; - -export type { DsChipGroupProps, ChipItem } from './ds-chip-group.types'; diff --git a/packages/design-system/src/components/ds-chip/ds-chip.module.scss b/packages/design-system/src/components/ds-chip/ds-chip.module.scss deleted file mode 100644 index dbf2cbde4..000000000 --- a/packages/design-system/src/components/ds-chip/ds-chip.module.scss +++ /dev/null @@ -1,71 +0,0 @@ -@use '../../styles/typography'; - -.chip { - display: inline-flex; - align-items: center; - gap: var(--3xs); - padding: 1px var(--3xs) 1px var(--xs); - background: var(--background); - border: 1px solid var(--border-contrast); - border-radius: 100vmax; - color: var(--font-main); - cursor: default; - overflow: hidden; - transition: - background-color 0.2s, - border-color 0.2s, - color 0.2s; - - &.clickable { - cursor: pointer; - - &:hover { - background: var(--background-action-hover-weak); - border-color: var(--border-action-secondary-hover); - } - - &:focus-visible { - outline: 2px solid var(--background-primary); - outline-offset: 2px; - } - } - - &.small { - padding: 0 var(--4xs) 0 var(--3xs); - gap: var(--4xs); - } - - &[aria-pressed='true'] { - background: var(--background-secondary-hover); - border-color: var(--border-action-primary); - color: var(--font-action); - } -} - -.label { - @include typography.ellipsis; - padding: var(--4xs) var(--3xs); - flex: 1; -} - -.deleteButton { - display: flex; - align-items: center; - justify-content: center; - border: none; - background: transparent; - color: var(--icon-information-main); - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; - flex-shrink: 0; - - &:hover { - color: var(--icon-action-secondary); - } - - .chip.small & { - padding: 1px var(--4xs); - } -} diff --git a/packages/design-system/src/components/ds-chip/ds-chip.stories.ts b/packages/design-system/src/components/ds-chip/ds-chip.stories.ts deleted file mode 100644 index 250f6e463..000000000 --- a/packages/design-system/src/components/ds-chip/ds-chip.stories.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import DsChip from './ds-chip'; -import { chipSizes } from './ds-chip.types'; - -/** - * @deprecated This component is deprecated. Use `DsTag` instead. - * @see {@link ../ds-tag/ds-tag.stories} for examples of the replacement component. - */ -const meta: Meta = { - title: 'Components/Chip (Deprecated)', - component: DsChip, - parameters: { - layout: 'centered', - docs: { - description: { - component: - '**Deprecated**: This component is deprecated. Please use `DsTag` instead. See the Tag stories for the replacement component.', - }, - }, - }, - tags: ['deprecated'], - argTypes: { - label: { - control: 'text', - description: 'The label text to display in the chip', - }, - className: { - control: 'text', - description: 'Additional CSS class names', - }, - style: { - control: 'object', - description: 'Inline styles to apply to the component', - }, - onClick: { - action: 'clicked', - description: 'Function called when component is clicked', - }, - onDelete: { - action: 'deleted', - description: 'Function called when delete icon is clicked', - }, - size: { - control: 'select', - options: chipSizes, - description: 'Size of the chip', - }, - deleteIcon: { - control: false, - description: 'Custom delete icon element', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - label: 'Default Label', - }, -}; diff --git a/packages/design-system/src/components/ds-chip/ds-chip.tsx b/packages/design-system/src/components/ds-chip/ds-chip.tsx deleted file mode 100644 index b2b4b422b..000000000 --- a/packages/design-system/src/components/ds-chip/ds-chip.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type React from 'react'; -import classNames from 'classnames'; -import styles from './ds-chip.module.scss'; -import type { DsChipProps } from './ds-chip.types'; -import { DsIcon } from '../ds-icon'; -import { DsTypography } from '../ds-typography'; - -/** - * @deprecated This component is deprecated. Use `DsTag` instead. - * @see {@link ../ds-tag} for the replacement component. - */ -const DsChip: React.FC = ({ - ref, - label, - className, - style = {}, - onClick, - onDelete, - size = 'medium', - deleteIcon, - selected = false, -}) => { - const chipClass = classNames( - styles.chip, - { - [styles.clickable]: onClick !== undefined, - [styles.small]: size === 'small', - }, - className, - ); - - const handleDeleteClick = (event: React.MouseEvent) => { - event.stopPropagation(); - onDelete?.(event); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (onDelete && (event.key === 'Backspace' || event.key === 'Delete')) { - event.preventDefault(); - onDelete(event); - } - }; - - return ( -
} - className={chipClass} - style={style} - onClick={onClick} - onKeyDown={handleKeyDown} - role={onClick || onDelete ? 'button' : undefined} - tabIndex={onClick || onDelete ? 0 : undefined} - aria-label={label} - aria-pressed={onClick && selected ? 'true' : undefined} - > - - {label} - - {onDelete && ( - - )} -
- ); -}; - -export default DsChip; diff --git a/packages/design-system/src/components/ds-chip/ds-chip.types.ts b/packages/design-system/src/components/ds-chip/ds-chip.types.ts deleted file mode 100644 index a466c63ff..000000000 --- a/packages/design-system/src/components/ds-chip/ds-chip.types.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { CSSProperties, KeyboardEvent, MouseEvent, Ref } from 'react'; -import type { IconType } from '../ds-icon'; - -/** - * @deprecated This type is deprecated. Use `DsTag` instead. - * @see {@link ../ds-tag} for the replacement component. - */ -export const chipSizes = ['medium', 'small'] as const; - -/** - * @deprecated This type is deprecated. Use `DsTag` instead. - * @see {@link ../ds-tag} for the replacement component. - */ -export type ChipSize = (typeof chipSizes)[number]; - -/** - * @deprecated This interface is deprecated. Use `DsTagProps` from `ds-tag` instead. - * @see {@link ../ds-tag/ds-tag.types} for the replacement interface. - */ -export interface DsChipProps { - /** - * Ref to the chip element - */ - ref?: Ref; - /** - * The label text to display in the chip - */ - label: string; - /** - * Additional CSS class names - */ - className?: string; - /** - * Additional styles to apply to the component - */ - style?: CSSProperties; - /** - * Optional click handler - */ - onClick?: (event: MouseEvent) => void; - /** - * Callback function when delete icon is clicked - */ - onDelete?: (event: MouseEvent | KeyboardEvent) => void; - /** - * Size of the chip - * @default 'medium' - */ - size?: ChipSize; - /** - * Custom delete icon element - */ - deleteIcon?: IconType; - /** - * Whether the chip is in a selected/pressed state - * @default false - */ - selected?: boolean; -} diff --git a/packages/design-system/src/components/ds-chip/index.ts b/packages/design-system/src/components/ds-chip/index.ts deleted file mode 100644 index 6d7de51e9..000000000 --- a/packages/design-system/src/components/ds-chip/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @deprecated DsChip is deprecated. Use DsTag instead. - * @see {@link ../ds-tag} for the replacement component. - */ -export { default as DsChip } from './ds-chip'; -/** - * @deprecated These types are deprecated. Use DsTag types instead. - * @see {@link ../ds-tag} for the replacement types. - */ -export type { ChipSize, DsChipProps } from './ds-chip.types'; diff --git a/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.stories.tsx b/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.stories.tsx index 010c5d038..abdb129e5 100644 --- a/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.stories.tsx +++ b/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.stories.tsx @@ -465,8 +465,8 @@ Interactive story demonstrating the filter functionality. - **Date range**: Filter by creation date - **Labels**: Filter by tags (Bug, High Priority, Feature Request, Documentation, Enhancement, Question) 3. Click **Apply** to see the filtered results -4. Selected filters appear as chips below the toolbar -5. Click on a chip to remove that filter, or use **Clear all** to remove all filters +4. Selected filters appear as tags below the toolbar +5. Click on a tag to remove that filter, or use **Clear all** to remove all filters **Current mock data:** - 4 unresolved comments with various authors and labels diff --git a/packages/design-system/src/components/ds-select/ds-select.tsx b/packages/design-system/src/components/ds-select/ds-select.tsx index 55e489a70..7dad6a7ce 100644 --- a/packages/design-system/src/components/ds-select/ds-select.tsx +++ b/packages/design-system/src/components/ds-select/ds-select.tsx @@ -6,10 +6,10 @@ import styles from './ds-select.module.scss'; import type { DsSelectOption, DsSelectProps } from './ds-select.types'; import { DsIcon } from '../ds-icon'; import { type DsCheckboxProps, DsCheckbox } from '../ds-checkbox'; -import { SelectItemsChips } from './select-items-chips'; import { DsTypography } from '../ds-typography'; import { DsTextInput } from '../ds-text-input'; import { SELECT_ALL_VALUE, getUserSelectedItems } from './utils'; +import {SelectItemsTags} from "./select-items-tags"; const SEARCH_THRESHOLD = 13; @@ -202,7 +202,7 @@ const DsSelect = ({ )} {multiselectProps.multiple && ( - setShowAllItems(true)} diff --git a/packages/design-system/src/components/ds-select/ds-select.types.ts b/packages/design-system/src/components/ds-select/ds-select.types.ts index 8e40c24a5..5a8ecd5c3 100644 --- a/packages/design-system/src/components/ds-select/ds-select.types.ts +++ b/packages/design-system/src/components/ds-select/ds-select.types.ts @@ -60,7 +60,7 @@ export type DsSelectProps = { /** * Custom render function for dropdown options. * When provided, replaces the default label text inside each dropdown item. - * The string `label` is still used for search, trigger text, chips, and accessibility. + * The string `label` is still used for search, trigger text, tags, and accessibility. */ renderOption?: (option: DsSelectOption) => ReactNode; } & ( diff --git a/packages/design-system/src/components/ds-select/select-items-chips.tsx b/packages/design-system/src/components/ds-select/select-items-tags.tsx similarity index 75% rename from packages/design-system/src/components/ds-select/select-items-chips.tsx rename to packages/design-system/src/components/ds-select/select-items-tags.tsx index d0147d971..16657260c 100644 --- a/packages/design-system/src/components/ds-select/select-items-chips.tsx +++ b/packages/design-system/src/components/ds-select/select-items-tags.tsx @@ -1,18 +1,17 @@ import { useSelectContext, type UseSelectContext } from '@ark-ui/react/select'; import styles from './ds-select.module.scss'; import { DsButton } from '../ds-button'; -// TODO: Use DsTag instead. -import { DsChip } from '../ds-chip'; +import { DsTag } from '../ds-tag'; import type { DsSelectOption, SelectOptionValue } from './ds-select.types'; -type SelectItemsChipsProps = { +type SelectItemsTagsProps = { showAll: boolean; onValueChange?: (value: SelectOptionValue[]) => void; onShowAll: () => void; count: number; }; -export function SelectItemsChips({ showAll, onShowAll, onValueChange, count }: SelectItemsChipsProps) { +export function SelectItemsTags({ showAll, onShowAll, onValueChange, count }: SelectItemsTagsProps) { const { collection, value: selectedItems } = useSelectContext() as UseSelectContext; if (!selectedItems.length) { @@ -36,11 +35,11 @@ export function SelectItemsChips({ showAll, onShowAll, onValueChange, count }: S onValueChange?.(filteredValue); }; - return ; + return ; })} {!showAll && selectedItems.length > count && ( - + )} [] = [ ]; function TableWithColumnFilters() { - const { columnFilters, filterChips, enhancedColumns, handlers } = useTableFilters({ + const { columnFilters, filterTags, enhancedColumns, handlers } = useTableFilters({ filterAdapters: [typeFilter], baseColumns, }); return (
- {filterChips.length > 0 && ( - + {filterTags.length > 0 && ( + )}
diff --git a/packages/design-system/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx b/packages/design-system/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx index 1a98089fa..28a6842e1 100644 --- a/packages/design-system/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +++ b/packages/design-system/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx @@ -17,7 +17,7 @@ export interface CheckboxFilterAdapterConfig { /** Custom row renderer; defaults to `item.label`. */ renderer?: (item: CheckboxFilterItem) => ReactNode; /** @default (item) => `${label}: ${item.label}` */ - chipLabelTemplate?: (item: CheckboxFilterItem) => string; + tagLabelTemplate?: (item: CheckboxFilterItem) => string; cellRenderer?: (value: TValue) => ReactNode; /** @default (row) => row.getValue(id) */ getRowValue?: (row: Row) => TValue; @@ -40,7 +40,7 @@ export function createCheckboxFilterAdapter( label, items, renderer, - chipLabelTemplate = (item) => `${label}: ${item.label}`, + tagLabelTemplate = (item) => `${label}: ${item.label}`, cellRenderer, getRowValue = (row) => row.getValue(id), searchable = false, @@ -64,10 +64,10 @@ export function createCheckboxFilterAdapter( cellRenderer, - toChips: (selectedItems) => { + toTags: (selectedItems) => { return selectedItems.map((item) => ({ id: `${id}_${String(item.value)}`, - label: chipLabelTemplate(item), + label: tagLabelTemplate(item), metadata: { key: id, value: item.value, @@ -75,8 +75,8 @@ export function createCheckboxFilterAdapter( })); }, - fromChip: (chip, currentValue) => { - return currentValue.filter((item) => item.value !== chip.metadata?.value); + fromTag: (tag, currentValue) => { + return currentValue.filter((item) => item.value !== tag.metadata?.value); }, getActiveFiltersCount: (selectedItems) => { diff --git a/packages/design-system/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx b/packages/design-system/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx index 41c855147..05f619db5 100644 --- a/packages/design-system/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +++ b/packages/design-system/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx @@ -70,8 +70,8 @@ export function createDualRangeFilterAdapter( cellRenderer: undefined, - toChips: (value) => { - const chips: TagFilterItem[] = []; + toTags: (value) => { + const tags: TagFilterItem[] = []; Object.entries(value).forEach(([fieldKey, range]) => { const hasFilter = range.from !== undefined || range.to !== undefined; @@ -81,7 +81,7 @@ export function createDualRangeFilterAdapter( const fromText = range.from !== undefined ? formatNumber(range.from) : ''; const toText = range.to !== undefined ? formatNumber(range.to) : ''; - chips.push({ + tags.push({ id: `${id}_${fieldKey}`, label: `${fieldLabel}: From ${fromText} to ${toText}`, metadata: { @@ -94,11 +94,11 @@ export function createDualRangeFilterAdapter( } }); - return chips; + return tags; }, - fromChip: (chip, currentValue) => { - const fieldKey = chip.metadata?.field as string; + fromTag: (tag, currentValue) => { + const fieldKey = tag.metadata?.field as string; if (!fieldKey) { return currentValue; } diff --git a/packages/design-system/src/components/ds-table/filters/adapters/create-filter-adapter.ts b/packages/design-system/src/components/ds-table/filters/adapters/create-filter-adapter.ts index eb7432898..805f34ab0 100644 --- a/packages/design-system/src/components/ds-table/filters/adapters/create-filter-adapter.ts +++ b/packages/design-system/src/components/ds-table/filters/adapters/create-filter-adapter.ts @@ -8,8 +8,8 @@ export interface FilterAdapterConfig initialValue: TFilterValue; /** TanStack `filterFn` for this column. */ filterFn: FilterAdapter['columnFilterFn']; - toChips: FilterAdapter['toChips']; - fromChip: FilterAdapter['fromChip']; + toTags: FilterAdapter['toTags']; + fromTag: FilterAdapter['fromTag']; getActiveFiltersCount: FilterAdapter['getActiveFiltersCount']; renderFilter: (value: TFilterValue, onChange: (value: TFilterValue) => void) => ReactNode; /** Cell renderer; receives the cell value, not the filter value. */ @@ -19,7 +19,7 @@ export interface FilterAdapterConfig /** * Base factory used by every specialized adapter. Reach for it only when * `createCheckboxFilterAdapter` / `createDualRangeFilterAdapter` don't fit - * (custom UI, mixed sub-filters, bespoke chip logic). + * (custom UI, mixed sub-filters, bespoke tag logic). */ export function createFilterAdapter( config: FilterAdapterConfig, @@ -30,8 +30,8 @@ export function createFilterAdapter( initialValue: config.initialValue, columnFilterFn: config.filterFn, cellRenderer: config.cellRenderer, - toChips: config.toChips, - fromChip: config.fromChip, + toTags: config.toTags, + fromTag: config.fromTag, getActiveFiltersCount: config.getActiveFiltersCount, reset: () => config.initialValue, renderFilter: config.renderFilter, diff --git a/packages/design-system/src/components/ds-table/filters/hooks/use-table-filters.ts b/packages/design-system/src/components/ds-table/filters/hooks/use-table-filters.ts index b7e568c26..19e327a68 100644 --- a/packages/design-system/src/components/ds-table/filters/hooks/use-table-filters.ts +++ b/packages/design-system/src/components/ds-table/filters/hooks/use-table-filters.ts @@ -40,8 +40,8 @@ export interface UseTableFiltersResult { filterState: FilterState; /** Applied filters mapped to TanStack's column-filters shape. */ columnFilters: ColumnFilterState[]; - /** Chips for `DsTagFilter`, derived from applied state. */ - filterChips: TagFilterItem[]; + /** Tags for `DsTagFilter`, derived from applied state. */ + filterTags: TagFilterItem[]; /** Filter-nav items with active counts. */ filterNavItems: FilterNavItem[]; /** `baseColumns` with `filterFn`, cell renderer and per-column meta wired in. */ @@ -53,8 +53,8 @@ export interface UseTableFiltersResult { applyFilters: () => void; /** Clear all filters. */ clearAll: () => void; - /** Remove a chip's effect from applied state. */ - deleteChip: (chip: TagFilterItem) => void; + /** Remove a tag's effect from applied state. */ + deleteTag: (tag: TagFilterItem) => void; /** Commit one column's draft (per-column popover Save). */ applyColumnFilter: (filterId: string) => void; /** Discard one column's draft without committing. */ @@ -103,9 +103,9 @@ export function useTableFilters({ }) .map(([id, value]) => ({ id, value })); - const filterChips = _filterAdapters.flatMap((adapter) => { + const filterTags = _filterAdapters.flatMap((adapter) => { const value = appliedFilters[adapter.id]; - return value !== undefined ? adapter.toChips(value) : []; + return value !== undefined ? adapter.toTags(value) : []; }); const filterNavItems: FilterNavItem[] = _filterAdapters.map((adapter) => ({ @@ -213,8 +213,8 @@ export function useTableFilters({ } }; - const deleteChip = (chip: TagFilterItem) => { - const filterKey = typeof chip.metadata?.key === 'string' ? chip.metadata.key : undefined; + const deleteTag = (tag: TagFilterItem) => { + const filterKey = typeof tag.metadata?.key === 'string' ? tag.metadata.key : undefined; if (!filterKey) { return; } @@ -229,7 +229,7 @@ export function useTableFilters({ return; } - const newValue = adapter.fromChip(chip, currentValue); + const newValue = adapter.fromTag(tag, currentValue); const newFilters = adapter.getActiveFiltersCount(newValue) === 0 @@ -257,14 +257,14 @@ export function useTableFilters({ return { filterState: draftFilters, columnFilters, - filterChips, + filterTags, filterNavItems, enhancedColumns, handlers: { updateFilter, applyFilters, clearAll, - deleteChip, + deleteTag, applyColumnFilter, cancelColumnFilter, clearColumnFilter, diff --git a/packages/design-system/src/components/ds-table/filters/types/filter-adapter.types.ts b/packages/design-system/src/components/ds-table/filters/types/filter-adapter.types.ts index 2a24de768..b9ee01952 100644 --- a/packages/design-system/src/components/ds-table/filters/types/filter-adapter.types.ts +++ b/packages/design-system/src/components/ds-table/filters/types/filter-adapter.types.ts @@ -21,12 +21,12 @@ export interface FilterAdapter { /** Optional cell renderer; receives the cell value, not the filter value. */ cellRenderer?: (value: TCellValue) => ReactNode; /** - * Convert the filter value into chips for `DsTagFilter`. Each chip's - * `metadata.key` / `metadata.value` must let `fromChip` undo it. + * Convert the filter value into tags for `DsTagFilter`. Each tag's + * `metadata.key` / `metadata.value` must let `fromTag` undo it. */ - toChips: (value: TFilterValue) => TagFilterItem[]; - /** Remove a chip's effect from the current value. */ - fromChip: (chip: TagFilterItem, currentValue: TFilterValue) => TFilterValue; + toTags: (value: TFilterValue) => TagFilterItem[]; + /** Remove a tag's effect from the current value. */ + fromTag: (tag: TagFilterItem, currentValue: TFilterValue) => TFilterValue; /** Number of active sub-filters; `0` means the filter is inactive. */ getActiveFiltersCount: (value: TFilterValue) => number; /** Return the initial value. */ diff --git a/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx b/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx index e82d413e8..7040f657c 100644 --- a/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx +++ b/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx @@ -171,18 +171,18 @@ const columns: ColumnDef[] = [ ]; function DevicesTable({ rows }: { rows: DeviceRow[] }) { - const { columnFilters, filterChips, enhancedColumns, handlers } = useTableFilters({ + const { columnFilters, filterTags, enhancedColumns, handlers } = useTableFilters({ filterAdapters: [typeFilter, siteFilter], baseColumns: columns, }); return ( <> - {filterChips.length > 0 && ( + {filterTags.length > 0 && ( )} - {filterChips.length > 0 && ( + {filterTags.length > 0 && ( )} - {filterChips.length > 0 && ( + {filterTags.length > 0 && ( )} @@ -349,7 +349,7 @@ external state, leaving other filters untouched. type: [{ value: 'PP-LGX', label: 'PP-LGX' }], }); - const { columnFilters, filterChips, enhancedColumns, handlers } = useTableFilters({ + const { columnFilters, filterTags, enhancedColumns, handlers } = useTableFilters({ filterAdapters: adapters, baseColumns, appliedFilters, @@ -358,11 +358,11 @@ external state, leaving other filters untouched. return (
- {filterChips.length > 0 && ( + {filterTags.length > 0 && ( )} diff --git a/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx b/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx index e9117b35a..be3e5f3b2 100644 --- a/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx +++ b/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx @@ -239,7 +239,7 @@ A plug-and-play filter system using the **Filter Adapter Pattern** that eliminat - **Plug-and-play**: Add filters by adding to config array - **Type-safe**: Full TypeScript support -- **Automatic**: Chip generation, nav items, column enhancement +- **Automatic**: Tag generation, nav items, column enhancement - **Reusable**: Generic adapters work across tables - **Extensible**: Custom adapters for complex scenarios @@ -281,10 +281,10 @@ function MyTable() { const { columnFilters, // For TanStack Table - filterChips, // For DsTagFilter + filterTags, // For DsTagFilter filterNavItems, // For filter navigation (FilterNavItem[]) enhancedColumns, // Columns with filters - handlers, // { applyFilters, clearAll, deleteChip } + handlers, // { applyFilters, clearAll, deleteTag } renderFilterContent, // Render function } = useTableFilters({ filterAdapters: myFilters, @@ -295,11 +295,11 @@ function MyTable() { <> setIsModalOpen(true)} /> - {filterChips.length > 0 && ( + {filterTags.length > 0 && ( )} @@ -327,7 +327,7 @@ createCheckboxFilterAdapter({ label: 'Display Label', items: [{ value: 'val1', label: 'Label 1' }], renderer?: (item) => , // Optional - chipLabelTemplate?: (item) => \`\${item.label}\`, // Optional + tagLabelTemplate?: (item) => \`\${item.label}\`, // Optional cellRenderer?: (value) => , // Optional }); \`\`\` @@ -352,8 +352,8 @@ createCustomFilterAdapter({ label: 'Display Label', initialValue: { /* your state */ }, filterFn: (row, columnId, filterValue) => boolean, - toChips: (value) => TagFilterItem[], - fromChip: (chip, currentValue) => newValue, + toTags: (value) => TagFilterItem[], + fromTag: (tag, currentValue) => newValue, getActiveFiltersCount: (value) => number, // 0 means none active renderFilter: (value, onChange) => ReactNode, cellRenderer?: (value) => ReactNode, // Optional @@ -362,7 +362,7 @@ createCustomFilterAdapter({ ## What You Get Automatically -- Chip generation from filter state +- Tag generation from filter state - Filter nav items with active counts - Column enhancement with filter functions - State management across all filters @@ -411,7 +411,7 @@ This story demonstrates the complete filter system with: - **Status Filter**: Checkbox multi-select with custom rendering (status badges) - **Running/Completed Filter**: Dual-range numeric filter - **Category Filter**: Simple checkbox multi-select -- **Version Filter**: Checkbox with custom chip labels +- **Version Filter**: Checkbox with custom tag labels #### Key Implementation Details: @@ -424,10 +424,10 @@ This story demonstrates the complete filter system with: \`\`\`typescript const { columnFilters, // Pass to DsTable - filterChips, // Pass to DsTagFilter + filterTags, // Pass to DsTagFilter filterNavItems, // Pass to DsVerticalTabs in modal enhancedColumns, // Pass to DsTable (includes filter functions) - handlers, // { applyFilters, clearAll, deleteChip } + handlers, // { applyFilters, clearAll, deleteTag } renderFilterContent, // Render function for modal content } = useTableFilters({ filterAdapters: workflowFilters, @@ -437,7 +437,7 @@ This story demonstrates the complete filter system with: 3. **What's Handled Automatically**: - Filter state management - - Chip generation and deletion + - Tag generation and deletion - Nav item counts (updates in real-time) - Column enhancement with filter functions - Type-safe filter values @@ -504,8 +504,8 @@ See the story code for complete implementation with styles. 1. Click the filter icon to open the modal 2. Select filters in different categories 3. Notice the nav item counts update as you make changes -4. Click "Apply" to see filtered data and chips -5. Delete individual chips or clear all filters +4. Click "Apply" to see filtered data and tags +5. Delete individual tags or clear all filters #### Adding More Filters: To add a new filter, just add one adapter to \`workflowFilters\` array. No other changes needed! @@ -515,7 +515,7 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other }, render: function Render(args) { // useTableFilters hook orchestrates all filter logic - const { columnFilters, filterChips, filterNavItems, enhancedColumns, handlers, renderFilterContent } = + const { columnFilters, filterTags, filterNavItems, enhancedColumns, handlers, renderFilterContent } = useTableFilters({ filterAdapters: workflowFilters, baseColumns: args.columns, @@ -577,9 +577,9 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other />
- {/* Filter chips (automatically generated from filter state) */} - {filterChips.length > 0 && ( - + {/* Filter tags (automatically generated from filter state) */} + {filterTags.length > 0 && ( + )} {/* Table with enhanced columns (includes filter functions) */} @@ -680,7 +680,7 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other // 6. Apply filters await userEvent.click(screen.getByRole('button', { name: /apply/i })); - // Verify chips appear + // Verify tags appear await expect(canvas.getByText(/status: active/i)).toBeInTheDocument(); await expect(canvas.getByText(/status: running/i)).toBeInTheDocument(); await expect(canvas.getByText(/running.*0.*50/i)).toBeInTheDocument(); @@ -700,11 +700,11 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other await userEvent.click(screen.getByRole('button', { name: /apply/i })); - // 9. Delete individual chip. DsTagFilter renders a `Delete tag` X inside each - // chip; the chip itself is also role="button", so the nested button is + // 9. Delete individual tag. DsTagFilter renders a `Delete tag` X inside each + // tag; the tag itself is also role="button", so the nested button is // excluded from the accessibility tree — query by label directly. - const activeChip = canvas.getByRole('button', { name: /status: active/i }); - const deleteButton = within(activeChip).getByLabelText(/delete tag/i); + const activeTag = canvas.getByRole('button', { name: /status: active/i }); + const deleteButton = activeTag.querySelector('button[aria-label="Delete tag"]') as HTMLElement; await userEvent.click(deleteButton); await expect(canvas.queryByRole('button', { name: /status: active/i })).not.toBeInTheDocument(); @@ -737,7 +737,7 @@ This is useful for: \`\`\`typescript const [appliedFilters, setAppliedFilters] = useState({}); -const { filterChips, handlers } = useTableFilters({ +const { filterTags, handlers } = useTableFilters({ filterAdapters: workflowFilters, baseColumns: columns, appliedFilters, // External state @@ -754,7 +754,7 @@ The debug panel below shows the current filter state as JSON. // External filter state (controlled mode) const [appliedFilters, setAppliedFilters] = useState>({}); - const { columnFilters, filterChips, filterNavItems, enhancedColumns, handlers, renderFilterContent } = + const { columnFilters, filterTags, filterNavItems, enhancedColumns, handlers, renderFilterContent } = useTableFilters({ filterAdapters: workflowFilters, baseColumns: args.columns, @@ -821,8 +821,8 @@ The debug panel below shows the current filter state as JSON. /> - {filterChips.length > 0 && ( - + {filterTags.length > 0 && ( + )} @@ -896,7 +896,7 @@ The debug panel below shows the current filter state as JSON. // 2. Verify external state is updated (debug panel shows filter) await expect(canvas.getByText(/"status"/)).toBeInTheDocument(); - // 3. Verify chip appears + // 3. Verify tag appears await expect(canvas.getByText(/status: active/i)).toBeInTheDocument(); // 4. Clear all and verify state resets diff --git a/packages/design-system/src/components/ds-table/stories/filters-panel/workflow-filters.config.tsx b/packages/design-system/src/components/ds-table/stories/filters-panel/workflow-filters.config.tsx index 3ac723a68..9463acc39 100644 --- a/packages/design-system/src/components/ds-table/stories/filters-panel/workflow-filters.config.tsx +++ b/packages/design-system/src/components/ds-table/stories/filters-panel/workflow-filters.config.tsx @@ -6,18 +6,18 @@ * * 1. **Checkbox Filter** (statusFilterAdapter): * - Multi-select with custom rendering (status badges) - * - Custom chip labels + * - Custom tag labels * - Custom cell renderer * * 2. **Dual-Range Filter** (runningCompletedFilterAdapter): * - Multiple numeric range fields in one filter * - Number formatting - * - Automatic chip generation + * - Automatic tag generation * * 3. **Custom Filter** (lastEditedFilterAdapter): * - Complex filter with editor selection + date range * - Custom render component - * - Multiple chip types from one filter + * - Multiple tag types from one filter * - Custom cell renderer * * ## Usage Pattern: @@ -30,7 +30,7 @@ * export const workflowFilters = [myFilter, ...]; * * // 3. Use in component with useTableFilters hook - * const { columnFilters, filterChips, enhancedColumns, handlers } = + * const { columnFilters, filterTags, enhancedColumns, handlers } = * useTableFilters(workflowFilters, columns); * ``` * @@ -122,20 +122,20 @@ const renderStatusBadge = (status: DsStatus): ReactNode => { * * Demonstrates: * - Custom item rendering with DsStatusBadge - * - Custom chip label template + * - Custom tag label template * - Custom cell renderer for table column * * Features: * - Empty selection = show all (no filter applied) * - Selected items = show only those items - * - Automatic chip generation for each selected status + * - Automatic tag generation for each selected status */ const statusFilterAdapter = createCheckboxFilterAdapter({ id: 'status', label: 'Status', items: statusItems, renderer: (item) => renderStatusBadge(item.value), - chipLabelTemplate: (item) => `Status: ${item.label}`, + tagLabelTemplate: (item) => `Status: ${item.label}`, cellRenderer: (value) => renderStatusBadge(value), }); @@ -150,8 +150,8 @@ const statusFilterAdapter = createCheckboxFilterAdapter({ * Features: * - Each field can have independent from/to ranges * - All ranges must match (AND logic) - * - Automatic chip generation for each active range - * - Formatted numbers in chips + * - Automatic tag generation for each active range + * - Formatted numbers in tags */ const runningCompletedFilterAdapter = createDualRangeFilterAdapter({ id: 'runningCompleted', @@ -233,14 +233,14 @@ const parseTimestamp = (timestamp: string): Date => { * Demonstrates: * - Complex filter state (editor multi-select + time range) * - Custom filter function with multiple conditions - * - Multiple chip types from one filter + * - Multiple tag types from one filter * - Custom filter UI component * - Custom cell renderer * * Features: * - Filter by multiple editors (multi-select) * - Filter by time range (preset options + custom date range) - * - Separate chips for editors and time range + * - Separate tags for editors and time range * - Both conditions must match (AND logic) * * This is a reference implementation for building custom filters @@ -277,19 +277,17 @@ const lastEditedFilterAdapter = createFilterAdapter { - const chips = []; + toTags: (value) => { + const tags = []; - // Editor chips value.editors.forEach((editor) => { - chips.push({ + tags.push({ id: `editor-${editor}`, label: `Editor: ${editor}`, metadata: { key: 'lastEdited', type: 'editor', value: editor }, }); }); - // Time range chip if (value.timeRange) { let label = ''; switch (value.timeRange) { @@ -312,22 +310,22 @@ const lastEditedFilterAdapter = createFilterAdapter { - const { type, value: chipValue } = chip.metadata || {}; + fromTag: (tag, currentValue) => { + const { type, value: tagValue } = tag.metadata || {}; if (type === 'editor') { return { ...currentValue, - editors: currentValue.editors.filter((e) => e !== chipValue), + editors: currentValue.editors.filter((e) => e !== tagValue), }; } diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index fe6d67e25..d84b80850 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -9,8 +9,6 @@ export * from './components/ds-card'; export * from './components/ds-catalog-layout'; export * from './components/ds-checkbox'; export * from './components/ds-checkbox-group'; -export * from './components/ds-chip'; -export * from './components/ds-chip-group'; export * from './components/ds-comment-bubble'; export * from './components/ds-comment-card'; export * from './components/ds-comment-indicator'; diff --git a/packages/design-system/vitest.config.ts b/packages/design-system/vitest.config.ts index ccd04daf6..157ea3628 100644 --- a/packages/design-system/vitest.config.ts +++ b/packages/design-system/vitest.config.ts @@ -21,8 +21,6 @@ export default defineConfig({ 'dist/**', // deprecated components - '**/ds-chip/**', - '**/ds-chip-group/**', '**/ds-date-input/**', ], thresholds: { diff --git a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts index f245c60f3..0328d8f2e 100644 --- a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts +++ b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts @@ -53,44 +53,6 @@ ruleTester.run('no-deprecated-ds-button-empty-design', plugin.rules['no-deprecat ], }); -ruleTester.run('no-deprecated-ds-chip', plugin.rules['no-deprecated-ds-chip'], { - valid: ['', ''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsChip is deprecated. Use DsTag instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 8, - }, - ], - }, - ], -}); - -ruleTester.run('no-deprecated-ds-chip-group', plugin.rules['no-deprecated-ds-chip-group'], { - valid: ['', ''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsChipGroup is deprecated. Use DsTagFilter instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 13, - }, - ], - }, - ], -}); - ruleTester.run('no-deprecated-ds-date-input', plugin.rules['no-deprecated-ds-date-input'], { valid: ['', ''], diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 05c3eb5a6..62155fdc8 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -15,18 +15,6 @@ const eslintPlugin = createPlugin( message: `Omitting the design attribute for DsButton is not allowed. Pass 'design="v1.2"' so the new design is used.`, }, - { - name: 'no-deprecated-ds-chip', - selector: JSXElementName('DsChip'), - message: `DsChip is deprecated. Use DsTag instead.`, - }, - - { - name: 'no-deprecated-ds-chip-group', - selector: JSXElementName('DsChipGroup'), - message: `DsChipGroup is deprecated. Use DsTagFilter instead.`, - }, - { name: 'no-deprecated-ds-date-input', selector: JSXElementName('DsDateInput'), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e11454ba2..5ebb3a878 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,15 +196,9 @@ importers: '@radix-ui/react-collapsible': specifier: ^1.1.12 version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dialog': - specifier: ^1.1.16 - version: 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-tooltip': specifier: ^1.2.9 version: 1.2.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-visually-hidden': - specifier: ^1.2.5 - version: 1.2.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -2305,19 +2299,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.16': - resolution: {integrity: sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-dismissable-layer@1.1.12': resolution: {integrity: sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==} peerDependencies: @@ -2331,28 +2312,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.4': - resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.9': - resolution: {integrity: sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-id@1.1.2': resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} peerDependencies: @@ -3631,10 +3590,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -4092,9 +4047,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -4487,10 +4439,6 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} @@ -5638,36 +5586,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react@19.2.6: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} @@ -6417,26 +6335,6 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -8393,28 +8291,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-dialog@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-portal': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.5(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.15)(react@19.2.6) - aria-hidden: 1.2.6 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dismissable-layer@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.4 @@ -8428,23 +8304,6 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.15)(react@19.2.6)': - dependencies: - react: 19.2.6 - optionalDependencies: - '@types/react': 19.2.15 - - '@radix-ui/react-focus-scope@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.15)(react@19.2.6) - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-id@1.1.2(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -10043,10 +9902,6 @@ snapshots: argparse@2.0.1: {} - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -10526,8 +10381,6 @@ snapshots: detect-libc@2.1.2: {} - detect-node-es@1.1.0: {} - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -11001,8 +10854,6 @@ snapshots: get-east-asian-width@1.5.0: {} - get-nonce@1.0.1: {} - get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -12333,33 +12184,6 @@ snapshots: react-is@17.0.2: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.15)(react@19.2.6): - dependencies: - react: 19.2.6 - react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.15 - - react-remove-scroll@2.7.2(@types/react@19.2.15)(react@19.2.6): - dependencies: - react: 19.2.6 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.15)(react@19.2.6) - react-style-singleton: 2.2.3(@types/react@19.2.15)(react@19.2.6) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.15)(react@19.2.6) - use-sidecar: 1.1.3(@types/react@19.2.15)(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.15 - - react-style-singleton@2.2.3(@types/react@19.2.15)(react@19.2.6): - dependencies: - get-nonce: 1.0.1 - react: 19.2.6 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.15 - react@19.2.6: {} read-yaml-file@1.1.0: @@ -13141,21 +12965,6 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.3(@types/react@19.2.15)(react@19.2.6): - dependencies: - react: 19.2.6 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.15 - - use-sidecar@1.1.3(@types/react@19.2.15)(react@19.2.6): - dependencies: - detect-node-es: 1.1.0 - react: 19.2.6 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.15 - use-sync-external-store@1.6.0(react@19.2.6): dependencies: react: 19.2.6 From 7da35195e738f0a34a8629a7d0adf4eaa38ffd4e Mon Sep 17 00:00:00 2001 From: Micky Date: Thu, 23 Apr 2026 10:06:37 +0200 Subject: [PATCH 5/7] refactor(design-system)!: remove DsButton, DsButtonLegacy; DsButtonV3 becomes default [AR-53409] (#381) --- .changeset/ninety-signs-move.md | 8 ++ .../ds-alert-banner-global.stories.tsx | 14 +-- .../ds-alert-banner-inline.stories.tsx | 10 +- .../components/ds-button-v3/ds-button-v3.tsx | 55 ----------- .../ds-button-v3/ds-button-v3.types.ts | 65 ------------- .../src/components/ds-button-v3/index.ts | 6 -- .../__tests__/ds-button.browser.test.tsx} | 54 +++++------ .../ds-button.figma.ts} | 14 +-- .../ds-button.module.scss} | 0 .../ds-button.stories.module.scss} | 0 .../ds-button.stories.tsx} | 69 +++++++------ .../src/components/ds-button/ds-button.tsx | 66 +++++++++++-- .../components/ds-button/ds-button.types.ts | 80 ++++++++++++---- .../src/components/ds-button/index.ts | 20 +++- .../ds-button-legacy.stories.module.scss | 62 ------------ .../ds-button-legacy/ds-button-legacy.tsx | 29 ------ .../ds-button-legacy.types.ts | 41 -------- .../versions/ds-button-legacy/index.ts | 2 - .../__tests__/ds-button.browser.test.tsx | 49 ---------- .../versions/ds-button-new/ds-button-new.tsx | 51 ---------- .../ds-button-new/ds-button-new.types.ts | 51 ---------- .../ds-button/versions/ds-button-new/index.ts | 5 - .../ds-thread-item/ds-thread-item.stories.tsx | 6 +- .../ds-thread-item/ds-thread-item.tsx | 11 +-- .../ds-comment-bubble/ds-comment-bubble.tsx | 36 +++---- .../ds-comment-card/ds-comment-card.tsx | 12 +-- .../comments-filter-modal.tsx | 4 +- .../ds-comments-drawer/ds-comments-drawer.tsx | 14 +-- .../ds-date-picker/ds-date-picker.tsx | 17 ++-- .../ds-date-range-picker.tsx | 2 +- .../ds-drawer/ds-drawer.stories.tsx | 8 +- .../ds-dropdown-menu.stories.tsx | 14 ++- .../ds-expandable-text-input.tsx | 4 +- .../file-upload-item/file-upload-item.tsx | 12 +-- .../components/file-upload/file-upload.tsx | 2 +- .../ds-file-upload/ds-file-upload.stories.tsx | 10 +- .../components/ds-grid/ds-grid.stories.tsx | 2 +- .../components/ds-modal/ds-modal.stories.tsx | 25 +++-- .../__tests__/ds-panel.browser.test.tsx | 4 +- .../components/ds-panel/ds-panel.stories.tsx | 2 +- .../src/components/ds-select/ds-select.tsx | 2 +- .../ds-select/select-items-tags.tsx | 8 +- .../ds-split-button/ds-split-button.tsx | 4 +- .../ds-split-button/ds-split-button.types.ts | 4 +- .../ds-status-badge-v2.browser.test.tsx | 96 +++++++++++++++++++ .../components/ds-step-next-button.tsx | 14 ++- .../ds-stepper/ds-stepper.stories.tsx | 2 +- .../ds-table-bulk-actions.module.scss | 69 ++++++++----- .../ds-table-bulk-actions.tsx | 21 ++-- .../ds-table-row-expandable-cell.tsx | 3 +- .../components/range-filter/range-filter.tsx | 2 +- .../stories/filters-panel.stories.tsx | 46 +++++---- .../last-edited-filter/last-edited-filter.tsx | 4 +- .../ds-tag-filter/ds-tag-filter.tsx | 15 +-- .../ds-time-picker/ds-time-picker.tsx | 11 +-- .../components/ds-toast/ds-toast.stories.tsx | 34 ++----- packages/design-system/src/index.ts | 1 - .../src/__tests__/no-deprecated.test.ts | 42 -------- packages/eslint-plugin/src/index.ts | 12 --- 59 files changed, 504 insertions(+), 822 deletions(-) create mode 100644 .changeset/ninety-signs-move.md delete mode 100644 packages/design-system/src/components/ds-button-v3/ds-button-v3.tsx delete mode 100644 packages/design-system/src/components/ds-button-v3/ds-button-v3.types.ts delete mode 100644 packages/design-system/src/components/ds-button-v3/index.ts rename packages/design-system/src/components/{ds-button-v3/__tests__/ds-button-v3.browser.test.tsx => ds-button/__tests__/ds-button.browser.test.tsx} (82%) rename packages/design-system/src/components/{ds-button-v3/ds-button-v3.figma.ts => ds-button/ds-button.figma.ts} (81%) rename packages/design-system/src/components/{ds-button-v3/ds-button-v3.module.scss => ds-button/ds-button.module.scss} (100%) rename packages/design-system/src/components/{ds-button-v3/ds-button-v3.stories.module.scss => ds-button/ds-button.stories.module.scss} (100%) rename packages/design-system/src/components/{ds-button-v3/ds-button-v3.stories.tsx => ds-button/ds-button.stories.tsx} (79%) delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.module.scss delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/index.ts delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/__tests__/ds-button.browser.test.tsx delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/index.ts create mode 100644 packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx diff --git a/.changeset/ninety-signs-move.md b/.changeset/ninety-signs-move.md new file mode 100644 index 000000000..a0201eef8 --- /dev/null +++ b/.changeset/ninety-signs-move.md @@ -0,0 +1,8 @@ +--- +'@drivenets/design-system': major +'@drivenets/eslint-plugin-design-system': major +--- + +Remove deprecated `DsButton` and `DsButtonLegacy`. +Rename `DsButtonV3` to `DsButton` (it becomes the default `DsButton` component). +Remove deprecation rules for `DsButtonLegacy`. diff --git a/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-global.stories.tsx b/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-global.stories.tsx index 144923938..353abf0c0 100644 --- a/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-global.stories.tsx +++ b/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-global.stories.tsx @@ -61,10 +61,10 @@ export const InfoBlue: Story = { bit longer so that you can see how spacing within an alert works with this kind of content. - + Proceed - + Skip @@ -135,10 +135,10 @@ export const WithActions: Story = { a bit longer so that you can see how spacing within an alert works with this kind of content. - + Proceed - + Skip @@ -217,13 +217,13 @@ export const CustomBody: Story = { - + Fix Now - + View Details - + Ignore diff --git a/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-inline.stories.tsx b/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-inline.stories.tsx index 52db4b1b0..c7999350d 100644 --- a/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-inline.stories.tsx +++ b/packages/design-system/src/components/ds-alert-banner/stories/ds-alert-banner-inline.stories.tsx @@ -183,10 +183,10 @@ export const WithActions: Story = { bit longer so that you can see how spacing within an alert works with this kind of content. - + Proceed - + Skip @@ -255,13 +255,13 @@ export const CustomBody: Story = { - + Update Now - + Schedule Later - + Learn More diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.tsx b/packages/design-system/src/components/ds-button-v3/ds-button-v3.tsx deleted file mode 100644 index de8951c40..000000000 --- a/packages/design-system/src/components/ds-button-v3/ds-button-v3.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import classNames from 'classnames'; -import { DsIcon, type IconSize } from '../ds-icon'; -import { DsSpinner } from '../ds-spinner'; -import styles from './ds-button-v3.module.scss'; -import type { ButtonV3Size, DsButtonV3BaseProps } from './ds-button-v3.types.ts'; - -const iconSizeMap: Record = Object.freeze({ - large: 'small', - medium: 'tiny', - small: 'tiny', - tiny: 'tiny', -}); - -const DsButtonV3 = ({ - ref, - className, - style, - children, - icon, - disabled, - loading = false, - color = 'default', - variant = 'primary', - size = 'medium', - selected = false, - type = 'button', - ...rest -}: DsButtonV3BaseProps) => { - const isIconOnly = icon !== undefined && !children; - - return ( - - ); -}; - -export default DsButtonV3; diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.types.ts b/packages/design-system/src/components/ds-button-v3/ds-button-v3.types.ts deleted file mode 100644 index 9dc97d224..000000000 --- a/packages/design-system/src/components/ds-button-v3/ds-button-v3.types.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { ButtonHTMLAttributes, Ref } from 'react'; -import type { IconType } from '../ds-icon'; -import type { ResponsiveValue } from '../../utils/responsive'; - -export const buttonV3Variants = ['primary', 'secondary', 'tertiary'] as const; -export type ButtonV3Variant = (typeof buttonV3Variants)[number]; - -export const buttonV3Colors = ['default', 'error', 'light'] as const; -export type ButtonV3Color = (typeof buttonV3Colors)[number]; - -export const buttonV3Sizes = ['large', 'medium', 'small', 'tiny'] as const; -export type ButtonV3Size = (typeof buttonV3Sizes)[number]; - -export interface DsButtonV3BaseProps extends ButtonHTMLAttributes { - ref?: Ref; - - /** - * - `default` — standard light-UI palette - * - `error` — destructive / danger palette (red tones) - * - `light` — palette for dark-background surfaces (Figma **Type** onDark) - * @default 'default' - */ - color?: ButtonV3Color; - - /** - * Visual variant of the button: - * - `primary` — filled, highest-emphasis action - * - `secondary` — outlined, medium-emphasis action - * - `tertiary` — borderless, low-emphasis action (text-like) - * @default 'primary' - */ - variant?: ButtonV3Variant; - - /** - * Size of the button. Controls height, padding, font size, and icon size. - * @default 'medium' - */ - size?: ButtonV3Size; - - /** - * Whether the button is in a selected/pressed state. Used for toggle buttons - * and segmented controls. - * @default false - */ - selected?: boolean; - - /** - * Leading icon. When set without children, renders as icon-only (square) layout. - */ - icon?: IconType; - - /** - * Shows a spinner as the leading element and disables interaction. - * @default false - */ - loading?: boolean; -} - -export interface DsButtonV3Props extends Omit { - /** - * Size of the button. Accepts a static value or a responsive object. - * @default 'medium' - */ - size?: ResponsiveValue; -} diff --git a/packages/design-system/src/components/ds-button-v3/index.ts b/packages/design-system/src/components/ds-button-v3/index.ts deleted file mode 100644 index 780edd3f8..000000000 --- a/packages/design-system/src/components/ds-button-v3/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { withResponsiveProps } from '../../utils/responsive'; -import DsButtonV3Base from './ds-button-v3'; - -export const DsButtonV3 = withResponsiveProps(DsButtonV3Base, ['size']); - -export * from './ds-button-v3.types'; diff --git a/packages/design-system/src/components/ds-button-v3/__tests__/ds-button-v3.browser.test.tsx b/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx similarity index 82% rename from packages/design-system/src/components/ds-button-v3/__tests__/ds-button-v3.browser.test.tsx rename to packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx index e1bff38e6..bb6fed8ab 100644 --- a/packages/design-system/src/components/ds-button-v3/__tests__/ds-button-v3.browser.test.tsx +++ b/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx @@ -1,13 +1,13 @@ import { createRef } from 'react'; import { describe, expect, it, vi } from 'vitest'; import { page } from 'vitest/browser'; -import { DsButtonV3 } from '../index'; +import { DsButton } from '../index'; -describe('DsButtonV3', () => { +describe('DsButton', () => { it('calls onClick when clicked', async () => { const onClick = vi.fn(); - await page.render(Click me); + await page.render(Click me); await page.getByRole('button', { name: 'Click me' }).click(); @@ -18,9 +18,9 @@ describe('DsButtonV3', () => { const onClick = vi.fn(); await page.render( - + Click me - , +
, ); const button = page.getByRole('button', { name: 'Click me', disabled: true }); @@ -32,7 +32,7 @@ describe('DsButtonV3', () => { }); it('applies selected state', async () => { - await page.render(Label); + await page.render(Label); const button = page.getByRole('button', { name: 'Label' }); @@ -40,7 +40,7 @@ describe('DsButtonV3', () => { }); it('sets data-color for error palette', async () => { - await page.render(Delete); + await page.render(Delete); const button = page.getByRole('button', { name: 'Delete' }); @@ -48,7 +48,7 @@ describe('DsButtonV3', () => { }); it('applies iconOnly layout when icon is set without children', async () => { - await page.render(); + await page.render(); const button = page.getByRole('button', { name: 'Confirm' }); @@ -56,7 +56,7 @@ describe('DsButtonV3', () => { }); it('does not apply iconOnly layout when icon is set with children', async () => { - await page.render(Save); + await page.render(Save); const button = page.getByRole('button', { name: 'Save' }); @@ -64,7 +64,7 @@ describe('DsButtonV3', () => { }); it('renders native submit button type', async () => { - await page.render(Send); + await page.render(Send); const button = page.getByRole('button', { name: 'Send' }); @@ -72,13 +72,13 @@ describe('DsButtonV3', () => { }); it('merges className', async () => { - await page.render(X); + await page.render(X); await expect.element(page.getByRole('button', { name: 'X' })).toHaveClass('extra'); }); it('sets aria-busy and data-loading when loading', async () => { - await page.render(Save); + await page.render(Save); const button = page.getByRole('button', { name: 'Save' }); @@ -87,7 +87,7 @@ describe('DsButtonV3', () => { }); it('renders spinner instead of icon when loading', async () => { - await page.render(); + await page.render(); const button = page.getByRole('button', { name: 'Saving' }); @@ -102,9 +102,9 @@ describe('DsButtonV3', () => { const onClick = vi.fn(); await page.render( - + Save - , + , ); await page.getByRole('button', { name: 'Save' }).click({ force: true }); @@ -115,9 +115,9 @@ describe('DsButtonV3', () => { it('sets data-variant for each variant', async () => { for (const variant of ['primary', 'secondary', 'tertiary'] as const) { await page.render( - + Label - , + , ); await expect @@ -127,7 +127,7 @@ describe('DsButtonV3', () => { }); it('applies default props when none are specified', async () => { - await page.render(Default); + await page.render(Default); const button = page.getByRole('button', { name: 'Default' }); @@ -139,7 +139,7 @@ describe('DsButtonV3', () => { it('forwards ref to the button element', async () => { const ref = createRef(); - await page.render(Ref); + await page.render(Ref); expect(ref.current).toBeInstanceOf(HTMLButtonElement); expect(ref.current?.textContent).toBe('Ref'); @@ -149,9 +149,9 @@ describe('DsButtonV3', () => { const onClick = vi.fn(); await page.render( - + Saving - , + , ); const button = page.getByRole('button', { name: 'Saving', disabled: true }); @@ -164,9 +164,9 @@ describe('DsButtonV3', () => { it('loading + disabled shows spinner with disabled styling', async () => { await page.render( - + Saving - , + , ); const button = page.getByRole('button', { name: 'Saving', disabled: true }); @@ -178,9 +178,9 @@ describe('DsButtonV3', () => { it('selected + disabled does not remove selected styling', async () => { await page.render( - + Toggle - , + , ); const button = page.getByRole('button', { name: 'Toggle', disabled: true }); @@ -192,9 +192,9 @@ describe('DsButtonV3', () => { it('spreads rest props onto the button element', async () => { await page.render( - + Rest - , + , ); const button = page.getByRole('button', { name: 'Spread' }); diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.figma.ts b/packages/design-system/src/components/ds-button/ds-button.figma.ts similarity index 81% rename from packages/design-system/src/components/ds-button-v3/ds-button-v3.figma.ts rename to packages/design-system/src/components/ds-button/ds-button.figma.ts index 9e1e4d7d3..ecb1def0d 100644 --- a/packages/design-system/src/components/ds-button-v3/ds-button-v3.figma.ts +++ b/packages/design-system/src/components/ds-button/ds-button.figma.ts @@ -1,6 +1,6 @@ // url=https://www.figma.com/design/nha3m67y7S57cHCSuQO2gp/DAP-Design-System-1.2?node-id=31065-2352 -// source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-button-v3 -// component=DsButtonV3 +// source=https://github.com/drivenets/design-system/tree/main/packages/design-system/src/components/ds-button +// component=DsButton import figma from 'figma'; const instance = figma.selectedInstance; @@ -31,7 +31,7 @@ const iconOnly = instance.getEnum('Icon', { 'icon-only': true, text: false }); const structure = instance.findInstance('.button-structure', { traverseInstances: true }); const showIcon = structure.type === 'INSTANCE' ? structure.getBoolean('Show icon') : false; -// `DsButtonV3.icon` is an icon-name string. Swapped icons keep the placeholder layer +// `DsButton.icon` is an icon-name string. Swapped icons keep the placeholder layer // name (e.g. `DAP_GM_O_check_circle`), so resolve via Code Connect ID or metadata. const toIconName = (name: string): string => name @@ -89,11 +89,11 @@ const iconProp = icon ? figma.code` icon="${icon}"` : ''; export default { example: iconOnly - ? figma.code`` + ? figma.code`` : showIcon && icon - ? figma.code`${label}` - : figma.code`${label}`, - imports: ["import { DsButtonV3 } from '@drivenets/design-system'"], + ? figma.code`${label}` + : figma.code`${label}`, + imports: ["import { DsButton } from '@drivenets/design-system'"], id: 'ds-button-v3', metadata: { nestable: true }, }; diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.module.scss b/packages/design-system/src/components/ds-button/ds-button.module.scss similarity index 100% rename from packages/design-system/src/components/ds-button-v3/ds-button-v3.module.scss rename to packages/design-system/src/components/ds-button/ds-button.module.scss diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.stories.module.scss b/packages/design-system/src/components/ds-button/ds-button.stories.module.scss similarity index 100% rename from packages/design-system/src/components/ds-button-v3/ds-button-v3.stories.module.scss rename to packages/design-system/src/components/ds-button/ds-button.stories.module.scss diff --git a/packages/design-system/src/components/ds-button-v3/ds-button-v3.stories.tsx b/packages/design-system/src/components/ds-button/ds-button.stories.tsx similarity index 79% rename from packages/design-system/src/components/ds-button-v3/ds-button-v3.stories.tsx rename to packages/design-system/src/components/ds-button/ds-button.stories.tsx index 247291e71..6a0aeb4ca 100644 --- a/packages/design-system/src/components/ds-button-v3/ds-button-v3.stories.tsx +++ b/packages/design-system/src/components/ds-button/ds-button.stories.tsx @@ -1,27 +1,26 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import classNames from 'classnames'; import { fn } from 'storybook/test'; -import DsButtonV3 from './ds-button-v3'; -import { DsButtonV3 as DsButtonV3Wrapped } from './index'; +import { DsButton } from './index'; import { - type ButtonV3Color, - buttonV3Colors, - buttonV3Sizes, - type ButtonV3Variant, - buttonV3Variants, -} from './ds-button-v3.types'; -import storyStyles from './ds-button-v3.stories.module.scss'; - -const meta: Meta = { - title: 'Components/ButtonV3', - component: DsButtonV3, + type ButtonColor, + buttonColors, + buttonSizes, + type ButtonVariant, + buttonVariants, +} from './ds-button.types'; +import storyStyles from './ds-button.stories.module.scss'; + +const meta: Meta = { + title: 'Components/Button', + component: DsButton, parameters: { layout: 'centered', }, argTypes: { - color: { control: 'select', options: buttonV3Colors }, - variant: { control: 'select', options: buttonV3Variants }, - size: { control: 'select', options: buttonV3Sizes }, + color: { control: 'select', options: buttonColors }, + variant: { control: 'select', options: buttonVariants }, + size: { control: 'select', options: buttonSizes }, loading: { control: 'boolean' }, disabled: { control: 'boolean' }, className: { table: { disable: true } }, @@ -32,7 +31,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { args: { @@ -45,7 +44,7 @@ export const Default: Story = { }; const matrixRows = [ - ...buttonV3Variants.map((v) => ({ label: v, loading: false })), + ...buttonVariants.map((v) => ({ label: v, loading: false })), { label: 'loading', loading: true }, ]; @@ -69,13 +68,13 @@ const onDarkIconMatrixRows = [ { label: 'loading', icon: 'info', variant: 'primary', color: 'light', loading: true }, ] as const; -const MatrixGrid = ({ color }: { color?: ButtonV3Color }) => { +const MatrixGrid = ({ color }: { color?: ButtonColor }) => { const isOnDark = color === 'light'; return (
- {buttonV3Sizes.map((size) => ( + {buttonSizes.map((size) => ( { {label} - {buttonV3Sizes.map((size) => ( + {buttonSizes.map((size) => (
- {size !== 'tiny' ? 'Button' : undefined} - +
))}
@@ -124,15 +123,15 @@ const IconMatrixGrid = ({ rows: ReadonlyArray<{ label: string; icon: 'check_circle' | 'info' | 'delete' | 'keyboard_arrow_down' | 'home'; - variant: ButtonV3Variant; - color: ButtonV3Color; + variant: ButtonVariant; + color: ButtonColor; loading: boolean; }>; isOnDark?: boolean; }) => (
- {buttonV3Sizes.map((size) => ( + {buttonSizes.map((size) => ( - {buttonV3Sizes.map((size) => { + {buttonSizes.map((size) => { const ariaLabel = `${label} ${size}`; return (
- (
- + lg: large / md: small - + - + lg: medium / md: tiny - + - + static: medium - +
), }; diff --git a/packages/design-system/src/components/ds-button/ds-button.tsx b/packages/design-system/src/components/ds-button/ds-button.tsx index 5482465c3..949bae04d 100644 --- a/packages/design-system/src/components/ds-button/ds-button.tsx +++ b/packages/design-system/src/components/ds-button/ds-button.tsx @@ -1,12 +1,60 @@ -import { DsButtonLegacy } from './versions/ds-button-legacy'; -import { DsButtonNew } from './versions/ds-button-new'; -import type { DsButtonUnifiedProps } from './ds-button.types'; - -const DsButton = (props: DsButtonUnifiedProps) => { - if (props.design === 'v1.2') { - return ; - } - return ; +import classNames from 'classnames'; +import { DsIcon, type IconSize } from '../ds-icon'; +import { DsSpinner } from '../ds-spinner'; +import styles from './ds-button.module.scss'; +import type { ButtonSize, DsButtonProps } from './ds-button.types'; + +const iconSizeMap: Record = Object.freeze({ + large: 'small', + medium: 'tiny', + small: 'tiny', + tiny: 'tiny', +}); + +const DsButton = ({ + ref, + className, + style, + children, + icon, + disabled, + loading = false, + color = 'default', + variant = 'primary', + size = 'medium', + selected = false, + type = 'button', + slotProps, + ...rest +}: DsButtonProps) => { + const isIconOnly = icon !== undefined && !children; + + return ( + + ); }; export default DsButton; diff --git a/packages/design-system/src/components/ds-button/ds-button.types.ts b/packages/design-system/src/components/ds-button/ds-button.types.ts index 76ef2b5ac..78976db08 100644 --- a/packages/design-system/src/components/ds-button/ds-button.types.ts +++ b/packages/design-system/src/components/ds-button/ds-button.types.ts @@ -1,17 +1,63 @@ -import type { DsButtonProps as DsButtonLegacyProps } from './versions/ds-button-legacy/ds-button-legacy.types'; -import type { DsButtonProps as DsButtonNewProps } from './versions/ds-button-new/ds-button-new.types'; - -export type DsButtonUnifiedProps = - | (DsButtonLegacyProps & { - /** - * Selects the legacy button implementation. Accepts `DsButtonLegacyProps`. - * Omit or pass `'legacy'` to opt into this branch. - */ - design?: undefined | 'legacy'; - }) - | (DsButtonNewProps & { - /** - * Selects the new (v1.2) button implementation. Accepts `DsButtonNewProps`. - */ - design: 'v1.2'; - }); +import type { ButtonHTMLAttributes, Ref } from 'react'; +import type { DsIconProps, IconType } from '../ds-icon'; + +export const buttonVariants = ['primary', 'secondary', 'tertiary'] as const; +export type ButtonVariant = (typeof buttonVariants)[number]; + +export const buttonColors = ['default', 'error', 'light'] as const; +export type ButtonColor = (typeof buttonColors)[number]; + +export const buttonSizes = ['large', 'medium', 'small', 'tiny'] as const; +export type ButtonSize = (typeof buttonSizes)[number]; + +export interface DsButtonProps extends ButtonHTMLAttributes { + ref?: Ref; + + /** + * - `default` — standard light-UI palette + * - `error` — destructive / danger palette (red tones) + * - `light` — palette for dark-background surfaces (Figma **Type** onDark) + * @default 'default' + */ + color?: ButtonColor; + + /** + * Visual variant of the button: + * - `primary` — filled, highest-emphasis action + * - `secondary` — outlined, medium-emphasis action + * - `tertiary` — borderless, low-emphasis action (text-like) + * @default 'primary' + */ + variant?: ButtonVariant; + + /** + * Size of the button. Controls height, padding, font size, and icon size. + * @default 'medium' + */ + size?: ButtonSize; + + /** + * Whether the button is in a selected/pressed state. Used for toggle buttons + * and segmented controls. + * @default false + */ + selected?: boolean; + + /** + * Leading icon. When set without children, renders as icon-only (square) layout. + */ + icon?: IconType; + + /** + * Shows a spinner as the leading element and disables interaction. + * @default false + */ + loading?: boolean; + + /** + * Props forwarded to internal slots. Use to override defaults on the leading icon. + */ + slotProps?: { + icon?: Omit, 'icon'>; + }; +} diff --git a/packages/design-system/src/components/ds-button/index.ts b/packages/design-system/src/components/ds-button/index.ts index 26a914360..5a72a4fde 100644 --- a/packages/design-system/src/components/ds-button/index.ts +++ b/packages/design-system/src/components/ds-button/index.ts @@ -1,2 +1,18 @@ -export { default as DsButton } from './ds-button'; -export * from './ds-button.types'; +import type { ComponentProps } from 'react'; +import { withResponsiveProps } from '../../utils/responsive'; +import DsButtonBase from './ds-button'; + +export { + buttonVariants, + buttonColors, + buttonSizes, + type ButtonVariant, + type ButtonColor, + type ButtonSize, +} from './ds-button.types'; + +export const DsButton = withResponsiveProps(DsButtonBase, ['size']); + +DsButton.displayName = 'DsButton'; + +export type DsButtonProps = ComponentProps; diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.module.scss b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.module.scss deleted file mode 100644 index a3ea9e1dc..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -.row { - display: flex; - gap: 20px; - padding-bottom: 20px; - align-items: center; -} - -.combinationsContainer { - display: flex; - flex-direction: column; - gap: 20px; - margin: 20px; -} - -.schemaContainer { - display: flex; - gap: 20px; - border-bottom: 1px solid #eaeaea; -} - -.iconButton-small { - width: 26px; - height: 26px; -} - -.iconButton-medium { - width: 42px; - height: 42px; -} - -.iconButton-large { - width: 56px; - height: 56px; -} - -.schemaTitle { - font-size: 20px; - font-weight: 600; - margin: 0; - padding-bottom: 8px; - min-width: 150px; - text-transform: capitalize; - align-self: center; -} - -.variantContainer { - display: flex; - flex-direction: column; - gap: 16px; -} - -.variantTitle { - font-size: 16px; - font-weight: 500; - margin: 0; - color: #666; - text-transform: capitalize; -} - -.buttonShowcase { - text-transform: capitalize; -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx deleted file mode 100644 index 4b98c4b17..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import classNames from 'classnames'; -import type React from 'react'; -import styles from './ds-button-legacy.module.scss'; -import type { DsButtonProps } from './ds-button-legacy.types'; - -/** - * Design system Button component - * @deprecated Use [DsButton](../ds-button-new/ds-button-new.tsx) instead. - */ -const DsButton: React.FC = ({ - schema = 'primary', - variant = 'filled', - size = 'medium', - disabled = false, - className, - contentClassName, - children, - ...props -}) => { - const buttonClass = classNames(styles.button, styles[`${schema}-${variant}`], styles[size], className); - - return ( - - ); -}; - -export default DsButton; diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts deleted file mode 100644 index e87f759c4..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type React from 'react'; - -export const buttonSchemas = ['primary', 'secondary', 'error'] as const; -export type ButtonSchema = (typeof buttonSchemas)[number]; - -export const buttonVariants = ['filled', 'ghost', 'borderless', 'round', 'dashed'] as const; -export type ButtonVariant = (typeof buttonVariants)[number]; - -export const buttonSizes = ['large', 'medium', 'small'] as const; -export type ButtonSize = (typeof buttonSizes)[number]; - -export interface DsButtonProps extends React.ButtonHTMLAttributes { - /** - * Color schema of the button - * @default 'primary' - */ - schema?: ButtonSchema; - - /** - * Visual variant of the button - * @default 'filled' - */ - variant?: ButtonVariant; - - /** - * Size of the button - * @default 'medium' - */ - size?: ButtonSize; - - /** - * Whether the button is disabled - * @default false - */ - disabled?: boolean; - - /** - * Class name for the button content - */ - contentClassName?: string; -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/index.ts b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/index.ts deleted file mode 100644 index 46f7b822b..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as DsButtonLegacy } from './ds-button-legacy'; -export * from './ds-button-legacy.types'; diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/__tests__/ds-button.browser.test.tsx b/packages/design-system/src/components/ds-button/versions/ds-button-new/__tests__/ds-button.browser.test.tsx deleted file mode 100644 index 0843774f7..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/__tests__/ds-button.browser.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import DsButton from '../ds-button-new'; -import { page } from 'vitest/browser'; - -describe('DsButton', () => { - it('should trigger onClick handler when clicked', async () => { - // Arrange. - const onClick = vi.fn(); - - await page.render(Click me); - - // Act. - await page.getByRole('button', { name: 'Click me' }).click(); - - // Assert. - expect(onClick).toHaveBeenCalled(); - }); - - it('should support disabled state', async () => { - // Arrange. - const onClick = vi.fn(); - - await page.render( - - Click me - , - ); - - const button = page.getByRole('button', { name: 'Click me', disabled: true }); - - // Act. - await button.click({ force: true }); - - // Assert. - expect(onClick).not.toHaveBeenCalled(); - await expect.element(button).toBeDisabled(); - }); - - it('should support custom class names', async () => { - // Arrange. - const className = 'custom-class'; - - // Act. - await page.render(Click me); - - // Assert. - await expect.element(page.getByRole('button', { name: 'Click me' })).toHaveClass(className); - }); -}); diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx b/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx deleted file mode 100644 index c9908bc03..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import classNames from 'classnames'; -import type React from 'react'; -import { Children, isValidElement } from 'react'; - -import styles from './ds-button-new.module.scss'; -import type { DsButtonBaseProps } from './ds-button-new.types'; -import { DsIcon } from '../../../ds-icon'; - -const isIconOnly = (children: React.ReactNode) => { - if (Children.count(children) !== 1) { - return false; - } - - const childArray = Children.toArray(children); - const onlyChild = childArray[0]; - - return isValidElement(onlyChild) && onlyChild.type === DsIcon; -}; - -/** - * Design system Button component - */ -const DsButton: React.FC = ({ - buttonType, - variant = 'filled', - size = 'medium', - disabled = false, - className, - contentClassName, - children, - ...props -}) => { - const type = buttonType ?? (variant === 'ghost' ? 'secondary' : 'primary'); - const buttonClass = classNames( - styles.button, - { [styles.iconButton]: isIconOnly(children) }, - styles[size], - className, - - // Casting because we don't have all variations of classnames defined - styles[`${type}-${variant}` as keyof typeof styles], - ); - - return ( - - ); -}; - -export default DsButton; diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts b/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts deleted file mode 100644 index 31347b58d..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type React from 'react'; - -import type { ResponsiveValue } from '../../../../utils/responsive'; - -export const buttonTypes = ['primary', 'secondary', 'secondary-light', 'tertiary'] as const; -export type ButtonType = (typeof buttonTypes)[number]; - -export const buttonVariants = ['filled', 'ghost', 'danger', 'dark'] as const; -export type ButtonVariant = (typeof buttonVariants)[number]; - -export const buttonSizes = ['large', 'medium', 'small', 'tiny'] as const; -export type ButtonSize = (typeof buttonSizes)[number]; - -export interface DsButtonBaseProps extends React.ButtonHTMLAttributes { - /** - * Type of the button - * @default 'primary' - */ - buttonType?: ButtonType; - - /** - * Visual variant of the button - * @default 'filled' - */ - variant?: ButtonVariant; - - /** - * Size of the button - * @default 'medium' - */ - size?: ButtonSize; - - /** - * Whether the button is disabled - * @default false - */ - disabled?: boolean; - - /** - * Class name for the button content - */ - contentClassName?: string; -} - -export interface DsButtonProps extends Omit { - /** - * Size of the button. Accepts a static value or a responsive object. - * @default 'medium' - */ - size?: ResponsiveValue; -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/index.ts b/packages/design-system/src/components/ds-button/versions/ds-button-new/index.ts deleted file mode 100644 index fd2a5f46e..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { withResponsiveProps } from '../../../../utils/responsive'; -import DsButtonBase from './ds-button-new'; - -export const DsButtonNew = withResponsiveProps(DsButtonBase, ['size']); -export * from './ds-button-new.types'; diff --git a/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.stories.tsx b/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.stories.tsx index 910420a68..656e37747 100644 --- a/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.stories.tsx +++ b/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.stories.tsx @@ -312,7 +312,7 @@ export const ContentChangeWhileNotEditing: Story = { onDelete={fn()} /> - setContent(UPDATED_CONTENT)}> + setContent(UPDATED_CONTENT)}> Simulate external update @@ -353,7 +353,7 @@ export const ContentChangeWhileEditing: Story = { onDelete={fn()} /> - setContent(UPDATED_CONTENT)}> + setContent(UPDATED_CONTENT)}> Simulate external update @@ -398,7 +398,7 @@ export const ContentChangeWhileEditingThenCancel: Story = { onDelete={fn()} /> - setContent(UPDATED_CONTENT)}> + setContent(UPDATED_CONTENT)}> Simulate external update diff --git a/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx b/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx index 158fd4b41..c117be637 100644 --- a/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx +++ b/packages/design-system/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx @@ -79,10 +79,10 @@ export const DsThreadItem = ({ rows={3} />
- + Cancel - + Save
@@ -97,9 +97,7 @@ export const DsThreadItem = ({ {hasEditActions && ( - - - + {onEdit && ( @@ -122,8 +120,7 @@ export const DsThreadItem = ({ )} {onResolved && ( onResolved(id)} aria-label="Mark message as resolved" diff --git a/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.tsx b/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.tsx index d11a36bdf..7f173d915 100644 --- a/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.tsx +++ b/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.tsx @@ -128,9 +128,7 @@ export const DsCommentBubble = ({
- - - + {!hideActionRequired && ( @@ -155,27 +153,23 @@ export const DsCommentBubble = ({ {onResolve && ( onResolve()} aria-label="Resolve" - > - - + /> )}
{onClose && ( onClose()} aria-label="Close" - > - - + /> )}
@@ -209,15 +203,13 @@ export const DsCommentBubble = ({ {(isInitialMode || hasThread) && ( - - + /> )}
@@ -233,15 +225,13 @@ export const DsCommentBubble = ({ )} - - + />
)} diff --git a/packages/design-system/src/components/ds-comment-card/ds-comment-card.tsx b/packages/design-system/src/components/ds-comment-card/ds-comment-card.tsx index 8e7020c37..ebf8dac7f 100644 --- a/packages/design-system/src/components/ds-comment-card/ds-comment-card.tsx +++ b/packages/design-system/src/components/ds-comment-card/ds-comment-card.tsx @@ -101,9 +101,7 @@ export const DsCommentCard = ({ {showMoreMenu && ( - - - + {onToggleActionRequired && ( @@ -137,14 +135,12 @@ export const DsCommentCard = ({ {onResolve && ( - - + /> )} diff --git a/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx b/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx index 8359eacdd..67ab5d9f4 100644 --- a/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +++ b/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx @@ -204,12 +204,12 @@ export const CommentsFilterModal = ({ - + Clear all - + Apply diff --git a/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.tsx b/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.tsx index 819129864..9a5c835e7 100644 --- a/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.tsx +++ b/packages/design-system/src/components/ds-comments-drawer/ds-comments-drawer.tsx @@ -114,9 +114,7 @@ export const DsCommentsDrawer = ({ {commentCount} Comments
- - - +
@@ -136,19 +134,17 @@ export const DsCommentsDrawer = ({ /> - - + /> {hasResolved && ( - + {showResolved ? 'Hide' : 'Show'} resolved ({resolvedCount}) diff --git a/packages/design-system/src/components/ds-date-picker/ds-date-picker.tsx b/packages/design-system/src/components/ds-date-picker/ds-date-picker.tsx index 712806c60..0ee16bab4 100644 --- a/packages/design-system/src/components/ds-date-picker/ds-date-picker.tsx +++ b/packages/design-system/src/components/ds-date-picker/ds-date-picker.tsx @@ -11,7 +11,6 @@ import { validateDateString, } from './ds-date-picker.utils'; import { DsTimePicker } from '../ds-time-picker'; -import { DsIcon } from '../ds-icon'; import styles from './ds-date-picker.module.scss'; import { useControlled } from '../../utils/use-controlled'; import { DsTextInput } from '../ds-text-input'; @@ -148,27 +147,23 @@ const DsDatePicker = ({ {showClearButton && ( setValue(null)}> - - + /> )} - - + /> ), diff --git a/packages/design-system/src/components/ds-date-range-picker/ds-date-range-picker.tsx b/packages/design-system/src/components/ds-date-range-picker/ds-date-range-picker.tsx index c5c52e27c..cf1022bea 100644 --- a/packages/design-system/src/components/ds-date-range-picker/ds-date-range-picker.tsx +++ b/packages/design-system/src/components/ds-date-range-picker/ds-date-range-picker.tsx @@ -89,7 +89,7 @@ const DsDateRangePicker = ({ {showClearAll && ( - + {locale?.clearAllLabel ?? 'Clear all'} diff --git a/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx b/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx index 5a65d6a15..ef791915f 100644 --- a/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx +++ b/packages/design-system/src/components/ds-drawer/ds-drawer.stories.tsx @@ -129,10 +129,10 @@ export const Default: Story = { - + Cancel - + Save @@ -196,10 +196,10 @@ export const WithTabs: Story = { - + Cancel - + Save diff --git a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.stories.tsx b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.stories.tsx index e3efee0da..23cd6f7ff 100644 --- a/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.stories.tsx +++ b/packages/design-system/src/components/ds-dropdown-menu/ds-dropdown-menu.stories.tsx @@ -259,10 +259,10 @@ export const CheckboxList: Story = { )} - + Cancel - + Apply @@ -413,13 +413,13 @@ export const RadioList: Story = { ))} - + Reset - + Cancel - + Apply @@ -449,9 +449,7 @@ export const ActionMenu: Story = { return ( - - - + diff --git a/packages/design-system/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx b/packages/design-system/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx index 2d5db863e..08bfe5a03 100644 --- a/packages/design-system/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +++ b/packages/design-system/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx @@ -83,10 +83,8 @@ export function DsExpandableTextInput({ endAdornment: ( { setExpanded(false); diff --git a/packages/design-system/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx b/packages/design-system/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx index e05df44dc..643b9c6d4 100644 --- a/packages/design-system/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +++ b/packages/design-system/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx @@ -52,8 +52,7 @@ export const FileUploadItem: React.FC = ({ onRetry(id)} > @@ -66,8 +65,7 @@ export const FileUploadItem: React.FC = ({ onRemove?.(id)} > @@ -80,8 +78,7 @@ export const FileUploadItem: React.FC = ({ onCancel(id)} > @@ -94,8 +91,7 @@ export const FileUploadItem: React.FC = ({ onDelete?.(id)} > diff --git a/packages/design-system/src/components/ds-file-upload/components/file-upload/file-upload.tsx b/packages/design-system/src/components/ds-file-upload/components/file-upload/file-upload.tsx index b43a7b379..ce0e28cad 100644 --- a/packages/design-system/src/components/ds-file-upload/components/file-upload/file-upload.tsx +++ b/packages/design-system/src/components/ds-file-upload/components/file-upload/file-upload.tsx @@ -63,7 +63,7 @@ export const FileUpload: React.FC = ({ {dropzoneText} - + {triggerText} diff --git a/packages/design-system/src/components/ds-file-upload/ds-file-upload.stories.tsx b/packages/design-system/src/components/ds-file-upload/ds-file-upload.stories.tsx index ffaddb152..3e42c6457 100644 --- a/packages/design-system/src/components/ds-file-upload/ds-file-upload.stories.tsx +++ b/packages/design-system/src/components/ds-file-upload/ds-file-upload.stories.tsx @@ -121,16 +121,10 @@ export const Manual: Story = { {hasFiles && (
- uploadAll()} disabled={isUploading}> + uploadAll()} disabled={isUploading}> {isUploading ? 'Uploading...' : 'Upload All'} - clearFiles()} - disabled={isUploading} - > + clearFiles()} disabled={isUploading}> Clear All
diff --git a/packages/design-system/src/components/ds-grid/ds-grid.stories.tsx b/packages/design-system/src/components/ds-grid/ds-grid.stories.tsx index 874a592fc..5405c7363 100644 --- a/packages/design-system/src/components/ds-grid/ds-grid.stories.tsx +++ b/packages/design-system/src/components/ds-grid/ds-grid.stories.tsx @@ -113,7 +113,7 @@ export const NavigationLayout: Story = {
- setIsCollapsed(!isCollapsed)}> + setIsCollapsed(!isCollapsed)}> Toggle
diff --git a/packages/design-system/src/components/ds-modal/ds-modal.stories.tsx b/packages/design-system/src/components/ds-modal/ds-modal.stories.tsx index c0d9df68c..5ef57538e 100644 --- a/packages/design-system/src/components/ds-modal/ds-modal.stories.tsx +++ b/packages/design-system/src/components/ds-modal/ds-modal.stories.tsx @@ -83,7 +83,7 @@ export const Default: Story = {

Default Modal

The default variant has an inset header underline and no footer border.

- setIsOpen(true)}> + setIsOpen(true)}> Open Modal
@@ -98,10 +98,10 @@ export const Default: Story = { - setIsOpen(false)}> + setIsOpen(false)}> Cancel - setIsOpen(false)}> + setIsOpen(false)}> Confirm @@ -186,7 +186,7 @@ export const Divided: Story = { Click the button below to open a form modal. Fill out the form and click "Save Changes" to see the results displayed here.

- setIsOpen(true)}> + setIsOpen(true)}> Open Form Modal
@@ -218,8 +218,7 @@ export const Divided: Story = {
setSubmittedData(null)} className={styles.clearResultsButton} @@ -401,11 +400,11 @@ export const Divided: Story = { - + Reset

Custom Modal Demo

Click the button below to open a custom modal with custom header and footer content.

- setIsOpen(true)}> + setIsOpen(true)}> Open Custom Modal
@@ -782,7 +781,7 @@ export const WithIcon: Story = {

Modal with Icon

Example showing how to add an icon to the modal header.

- setIsOpen(true)}> + setIsOpen(true)}> Open Modal
@@ -838,7 +837,7 @@ export const WithoutHeader: Story = {

Modal Without Header

Modal with only body and footer content, no header section.

- setIsOpen(true)}> + setIsOpen(true)}> Open Modal
@@ -852,10 +851,10 @@ export const WithoutHeader: Story = { - setIsOpen(false)}> + setIsOpen(false)}> Close - setIsOpen(false)}> + setIsOpen(false)}> Continue diff --git a/packages/design-system/src/components/ds-panel/__tests__/ds-panel.browser.test.tsx b/packages/design-system/src/components/ds-panel/__tests__/ds-panel.browser.test.tsx index b5e35da19..a760da8de 100644 --- a/packages/design-system/src/components/ds-panel/__tests__/ds-panel.browser.test.tsx +++ b/packages/design-system/src/components/ds-panel/__tests__/ds-panel.browser.test.tsx @@ -3,14 +3,14 @@ import { describe, expect, it } from 'vitest'; import { page } from 'vitest/browser'; import { DsPanel } from '../'; -import { DsButtonV3 } from '../../ds-button-v3'; +import { DsButton } from '../../ds-button'; function CollapsiblePanel({ variant }: { variant?: 'docked' | 'floating' }) { const [open, setOpen] = useState(true); return ( <> - {!open && setOpen(true)}>Open Panel} + {!open && setOpen(true)}>Open Panel}

This is a panel

diff --git a/packages/design-system/src/components/ds-panel/ds-panel.stories.tsx b/packages/design-system/src/components/ds-panel/ds-panel.stories.tsx index b27f3f8c7..2a4c71b66 100644 --- a/packages/design-system/src/components/ds-panel/ds-panel.stories.tsx +++ b/packages/design-system/src/components/ds-panel/ds-panel.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { DsPanel } from './'; -import { DsButton } from '../ds-button/'; +import { DsButton } from '../ds-button'; import { DsStepper, DsStep, DsStepContent, DsNextStepButton } from '../ds-stepper'; import { useState } from 'react'; import type { DsPanelVariant } from './ds-panel.types'; diff --git a/packages/design-system/src/components/ds-select/ds-select.tsx b/packages/design-system/src/components/ds-select/ds-select.tsx index 7dad6a7ce..5242a5793 100644 --- a/packages/design-system/src/components/ds-select/ds-select.tsx +++ b/packages/design-system/src/components/ds-select/ds-select.tsx @@ -6,10 +6,10 @@ import styles from './ds-select.module.scss'; import type { DsSelectOption, DsSelectProps } from './ds-select.types'; import { DsIcon } from '../ds-icon'; import { type DsCheckboxProps, DsCheckbox } from '../ds-checkbox'; +import { SelectItemsTags } from './select-items-tags'; import { DsTypography } from '../ds-typography'; import { DsTextInput } from '../ds-text-input'; import { SELECT_ALL_VALUE, getUserSelectedItems } from './utils'; -import {SelectItemsTags} from "./select-items-tags"; const SEARCH_THRESHOLD = 13; diff --git a/packages/design-system/src/components/ds-select/select-items-tags.tsx b/packages/design-system/src/components/ds-select/select-items-tags.tsx index 16657260c..64aab1d53 100644 --- a/packages/design-system/src/components/ds-select/select-items-tags.tsx +++ b/packages/design-system/src/components/ds-select/select-items-tags.tsx @@ -42,13 +42,7 @@ export function SelectItemsTags({ showAll, onShowAll, onValueChange, count }: Se )} - onValueChange?.([])} - > + onValueChange?.([])}> Clear All diff --git a/packages/design-system/src/components/ds-split-button/ds-split-button.tsx b/packages/design-system/src/components/ds-split-button/ds-split-button.tsx index c4b4dff53..ced088be1 100644 --- a/packages/design-system/src/components/ds-split-button/ds-split-button.tsx +++ b/packages/design-system/src/components/ds-split-button/ds-split-button.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import { DsSelect, type SelectSize } from '../ds-select'; import styles from './ds-split-button.module.scss'; import type { DsSplitButtonProps, SplitButtonSize } from './ds-split-button.types'; -import { DsButtonV3 } from '../ds-button-v3'; +import { DsButton } from '../ds-button'; const DsSplitButton = ({ ref, @@ -18,7 +18,7 @@ const DsSplitButton = ({ return (
- ; +type ButtonSlotProps = Omit; type SelectSlotProps = DistributiveOmit; diff --git a/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx b/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx new file mode 100644 index 000000000..cc96752dd --- /dev/null +++ b/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx @@ -0,0 +1,96 @@ +import { createRef } from 'react'; +import { describe, expect, it } from 'vitest'; +import { page } from 'vitest/browser'; +import DsStatusBadgeV2 from '../ds-status-badge-v2'; +import { DsStatusBadgeV2 as DsStatusBadgeV2Responsive } from '../index'; + +describe('DsStatusBadgeV2', () => { + it('renders with role="status" and aria-label from label', async () => { + await page.render(); + + await expect.element(page.getByRole('status', { name: 'Active' })).toBeVisible(); + }); + + it('renders label text', async () => { + await page.render(); + + await expect.element(page.getByText('Reserved')).toBeVisible(); + }); + + it('renders default phase icon', async () => { + await page.render(); + + const badge = page.getByRole('status').element(); + expect(badge.querySelector('[aria-hidden="true"]')).toBeTruthy(); + }); + + it('hides icon when icon={null} (text-only)', async () => { + await page.render(); + + const badge = page.getByRole('status').element(); + expect(badge.querySelector('[aria-hidden="true"]')).toBeNull(); + + await expect.element(page.getByText('Active')).toBeVisible(); + }); + + it('renders custom icon override', async () => { + await page.render(); + + const badge = page.getByRole('status').element(); + const icon = badge.querySelector('[aria-hidden="true"]'); + expect(icon).toBeTruthy(); + expect(icon!.textContent).toBe('star'); + }); + + it('iconOnly hides label and shows tooltip with label text on hover', async () => { + await page.render(); + + await expect.element(page.getByText('Active')).not.toBeInTheDocument(); + + await page.getByRole('status', { name: 'Active' }).hover(); + await expect.element(page.getByRole('tooltip', { name: 'Active' })).toBeVisible(); + }); + + it('custom aria-label overrides label', async () => { + await page.render(); + + await expect.element(page.getByRole('status', { name: 'Custom status' })).toBeVisible(); + }); + + it('applies secondary variant class', async () => { + await page.render(); + + const badge = page.getByRole('status').element(); + expect(badge.classList.toString()).toMatch(/secondary/); + }); + + it('applies small size class', async () => { + await page.render(); + + const badge = page.getByRole('status').element(); + expect(badge.classList.toString()).toMatch(/small/); + }); + + it('merges className', async () => { + await page.render(); + + await expect.element(page.getByRole('status')).toHaveClass('extra'); + }); + + it('forwards ref', async () => { + const ref = createRef(); + + await page.render(); + + expect(ref.current).toBeInstanceOf(HTMLElement); + expect(ref.current?.getAttribute('role')).toBe('status'); + }); + + it('renders via responsive wrapper', async () => { + await page.render( + , + ); + + await expect.element(page.getByRole('status', { name: 'Testing' })).toBeVisible(); + }); +}); diff --git a/packages/design-system/src/components/ds-stepper/components/ds-step-next-button.tsx b/packages/design-system/src/components/ds-stepper/components/ds-step-next-button.tsx index 62b0c9809..3e030370f 100644 --- a/packages/design-system/src/components/ds-stepper/components/ds-step-next-button.tsx +++ b/packages/design-system/src/components/ds-stepper/components/ds-step-next-button.tsx @@ -1,21 +1,19 @@ import { useStepper } from '../hooks/use-stepper'; -import { DsButton, type DsButtonUnifiedProps } from '../../ds-button'; - -// TODO: Use the new button props once we deprecate the legacy button. -type DsLegacyButtonProps = Extract & { - design?: undefined | 'legacy'; -}; +import { DsButton, type DsButtonProps } from '../../ds-button'; export type DsNextStepButtonProps = Pick< - DsLegacyButtonProps, + DsButtonProps, 'variant' | 'size' | 'className' | 'style' | 'children' >; export function DsNextStepButton({ children, size = 'small', ...rest }: DsNextStepButtonProps) { const context = useStepper(); + // strip color — ark returns `string` which conflicts with DsButton union + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { color, ...triggerProps } = context.stepsApi.getNextTriggerProps(); return ( - + {children} ); diff --git a/packages/design-system/src/components/ds-stepper/ds-stepper.stories.tsx b/packages/design-system/src/components/ds-stepper/ds-stepper.stories.tsx index 15838ce14..454334198 100644 --- a/packages/design-system/src/components/ds-stepper/ds-stepper.stories.tsx +++ b/packages/design-system/src/components/ds-stepper/ds-stepper.stories.tsx @@ -360,7 +360,7 @@ export const CustomizedHorizontal: Story = { activeStep={activeStep} onStepChange={({ step }) => setActiveStep(step)} actions={ - + {activeStep === customSteps.length - 1 ? 'Finish' : 'Continue'} } diff --git a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss index 232451adf..50937011d 100644 --- a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +++ b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss @@ -1,17 +1,18 @@ @use '../../../../styles/typography'; +$border-radius: 8px; + .bulkActionsContainer { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); - background-color: var(--color-dap-gray-050); - border-radius: 8px; + background-color: var(--white); + border-radius: $border-radius; box-shadow: 0 4px 12px #0445cc66; z-index: 1; display: flex; align-items: center; - border-radius: 8px; overflow: hidden; animation-duration: 0.3s; animation-timing-function: ease-out; @@ -29,8 +30,6 @@ .bulkActionsContent { display: flex; align-items: center; - padding-right: 6px; - padding-left: 6px; } .selectedCountContainer { @@ -41,51 +40,71 @@ } .selectedCountBadge { - color: var(--color-dap-gray-050); + color: var(--white); margin: 0; } .bulkActionsInfo { - color: var(--color-dap-gray-600); + color: var(--neutral-2); text-wrap: nowrap; padding-left: 8px; padding-right: 56px; } +.actionButton { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-3xs); + width: 68px; + height: 48px; + border-radius: 0; +} + .bulkActionsActions { display: flex; align-items: center; - gap: 8px; > .actionButton { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--3xs); - border-right: 1px solid var(--color-dap-gray-400); - width: 68px; - height: 48px; - border-radius: 0; - } + position: relative; + + &::after { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 1px; + height: 100%; + background-color: var(--neutral-6); + } - > .actionButton:first-child { - border-left: 1px solid var(--color-dap-gray-400); + &:first-child::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 100%; + background-color: var(--neutral-6); + } } } -.escapeButtonContent { +.targetButton.escapeButtonContent { display: flex; flex-direction: column; justify-content: center; align-items: center; - color: var(--color-dap-gray-600); - gap: 2px !important; - width: 36px; + color: var(--neutral-2); padding-left: 4px; + margin-right: 2px; + background: none; + border-radius: 0 $border-radius $border-radius 0; &:hover { - color: var(--color-dap-gray-500); + color: var(--neutral-4); + background: inherit; } } diff --git a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx index 297000935..86a5b9560 100644 --- a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +++ b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx @@ -43,22 +43,25 @@ const DsTableBulkActions: React.FC = ({ numSelectedRows, actio
{actions.map((action, index) => ( - + ))}
- - + slotProps={{ + icon: { + size: 'medium', + }, + }} + />
); diff --git a/packages/design-system/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx b/packages/design-system/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx index 20e2758f9..84726ecd4 100644 --- a/packages/design-system/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx +++ b/packages/design-system/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx @@ -14,8 +14,7 @@ export const DsTableRowExpandableCell = ({ return ( { diff --git a/packages/design-system/src/components/ds-table/filters/components/range-filter/range-filter.tsx b/packages/design-system/src/components/ds-table/filters/components/range-filter/range-filter.tsx index 9d1aaa6fb..52483cec9 100644 --- a/packages/design-system/src/components/ds-table/filters/components/range-filter/range-filter.tsx +++ b/packages/design-system/src/components/ds-table/filters/components/range-filter/range-filter.tsx @@ -47,7 +47,7 @@ export const RangeFilter = ({ label, value, onChange, onClear }: RangeFilterProp
{label} {hasValue && onClear && ( - + Clear )} diff --git a/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx b/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx index be3e5f3b2..e29af9ab6 100644 --- a/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx +++ b/packages/design-system/src/components/ds-table/stories/filters-panel.stories.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import type { ColumnDef } from '@tanstack/react-table'; import { DsIcon } from '../../ds-icon'; import DsTable from '../ds-table'; -import { DsButtonV3 } from '../../ds-button-v3'; +import { DsButton } from '../../ds-button'; import { DsModal } from '../../ds-modal'; import { DsVerticalTabs } from '../../ds-vertical-tabs'; import { DsTypography } from '../../ds-typography'; @@ -293,7 +293,9 @@ function MyTable() { return ( <> - setIsModalOpen(true)} /> + setIsModalOpen(true)}> + + {filterTags.length > 0 && ( { - Clear all + Clear all - Apply + Apply @@ -569,12 +571,9 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other
{/* Toolbar with filter button */}
- setIsOpen(true)} - /> + setIsOpen(true)}> + +
{/* Filter tags (automatically generated from filter state) */} @@ -617,13 +616,14 @@ To add a new filter, just add one adapter to \`workflowFilters\` array. No other - + + Clear all - + - + Apply - + @@ -813,12 +813,9 @@ The debug panel below shows the current filter state as JSON.
- setIsOpen(true)} - /> + setIsOpen(true)}> + +
{filterTags.length > 0 && ( @@ -858,13 +855,14 @@ The debug panel below shows the current filter state as JSON. - + + Clear all - +
- + Apply - + diff --git a/packages/design-system/src/components/ds-table/stories/filters-panel/components/last-edited-filter/last-edited-filter.tsx b/packages/design-system/src/components/ds-table/stories/filters-panel/components/last-edited-filter/last-edited-filter.tsx index d79a2feda..4361c4e54 100644 --- a/packages/design-system/src/components/ds-table/stories/filters-panel/components/last-edited-filter/last-edited-filter.tsx +++ b/packages/design-system/src/components/ds-table/stories/filters-panel/components/last-edited-filter/last-edited-filter.tsx @@ -85,7 +85,7 @@ export const LastEditedFilter = ({ value, onChange, availableEditors }: LastEdit
Editor {value.editors.length > 0 && ( - + Clear )} @@ -126,7 +126,7 @@ export const LastEditedFilter = ({ value, onChange, availableEditors }: LastEdit
Last edited {value.timeRange && ( - + Clear )} diff --git a/packages/design-system/src/components/ds-tag-filter/ds-tag-filter.tsx b/packages/design-system/src/components/ds-tag-filter/ds-tag-filter.tsx index 9b242777d..44680b346 100644 --- a/packages/design-system/src/components/ds-tag-filter/ds-tag-filter.tsx +++ b/packages/design-system/src/components/ds-tag-filter/ds-tag-filter.tsx @@ -96,11 +96,8 @@ const DsTagFilter = ({
{hasOverflow && ( @@ -110,15 +107,7 @@ const DsTagFilter = ({ )} {onClearAll && ( - + {clearButton} diff --git a/packages/design-system/src/components/ds-time-picker/ds-time-picker.tsx b/packages/design-system/src/components/ds-time-picker/ds-time-picker.tsx index 70c4d0ffa..32ee34ebc 100644 --- a/packages/design-system/src/components/ds-time-picker/ds-time-picker.tsx +++ b/packages/design-system/src/components/ds-time-picker/ds-time-picker.tsx @@ -139,9 +139,8 @@ const DsTimePicker = (props: DsTimePickerProps) => { {showClearButton && ( setValue(null)} @@ -151,14 +150,12 @@ const DsTimePicker = (props: DsTimePickerProps) => { )} - - + /> ), diff --git a/packages/design-system/src/components/ds-toast/ds-toast.stories.tsx b/packages/design-system/src/components/ds-toast/ds-toast.stories.tsx index 7d89da608..832e52626 100644 --- a/packages/design-system/src/components/ds-toast/ds-toast.stories.tsx +++ b/packages/design-system/src/components/ds-toast/ds-toast.stories.tsx @@ -78,7 +78,7 @@ const ToastDemo = ({ return (

{variant.charAt(0).toUpperCase() + variant.slice(1)} Toast

- + Show {variant.charAt(0).toUpperCase() + variant.slice(1)} Toast
@@ -124,7 +124,7 @@ export const WarningNoTitleAction: Story = { variant="warning" description="Something went wrong. Please try again." actions={ - + Restart } @@ -149,23 +149,22 @@ export const WarningWithActions: Story = {
{ setAction('abort'); dismissToast(toastId); }} - variant="ghost" + variant="tertiary" > Abort { setAction('retry'); dismissToast(toastId); }} - variant="danger" + variant="primary" + color="error" > Re-try @@ -178,12 +177,7 @@ export const WarningWithActions: Story = { return (

Toast with Action

- + Show Toast with Actions {action &&

{action}

} @@ -262,7 +256,7 @@ export const LongContent: Story = { return (

Long Content Toast

- + Show Long Content Toast
@@ -325,20 +319,10 @@ const MultipleToastsDemo = () => {

Multiple Toasts Demo

- + Create Multiple Toasts - + Dismiss All
diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index d84b80850..d38320fea 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -4,7 +4,6 @@ export * from './components/ds-avatar'; export * from './components/ds-avatar-group'; export * from './components/ds-breadcrumb'; export * from './components/ds-button'; -export * from './components/ds-button-v3'; export * from './components/ds-card'; export * from './components/ds-catalog-layout'; export * from './components/ds-checkbox'; diff --git a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts index 0328d8f2e..7fe3988bd 100644 --- a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts +++ b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts @@ -11,48 +11,6 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run( - 'no-deprecated-ds-button-legacy-design', - plugin.rules['no-deprecated-ds-button-legacy-design'], - { - valid: ['Click me'], - - invalid: [ - { - code: 'Click me', - errors: [ - { - message: `Using the 'legacy' design for DsButton is deprecated. Use 'v1.2' instead.`, - line: 1, - endLine: 1, - column: 11, - endColumn: 26, - }, - ], - }, - ], - }, -); - -ruleTester.run('no-deprecated-ds-button-empty-design', plugin.rules['no-deprecated-ds-button-empty-design'], { - valid: ['Click me'], - - invalid: [ - { - code: 'Click me', - errors: [ - { - message: `Omitting the design attribute for DsButton is not allowed. Pass 'design="v1.2"' so the new design is used.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 10, - }, - ], - }, - ], -}); - ruleTester.run('no-deprecated-ds-date-input', plugin.rules['no-deprecated-ds-date-input'], { valid: ['', ''], diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 62155fdc8..d8d583f33 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -3,18 +3,6 @@ import { createPlugin } from './create-plugin'; const eslintPlugin = createPlugin( '@drivenets/design-system', - { - name: 'no-deprecated-ds-button-legacy-design', - selector: JSXElementAttribute('DsButton', 'design', 'legacy'), - message: `Using the 'legacy' design for DsButton is deprecated. Use 'v1.2' instead.`, - }, - - { - name: 'no-deprecated-ds-button-empty-design', - selector: `${JSXElement('DsButton')}:not(:has( > ${JSXAttribute('design')} )) > .name`, - message: `Omitting the design attribute for DsButton is not allowed. Pass 'design="v1.2"' so the new design is used.`, - }, - { name: 'no-deprecated-ds-date-input', selector: JSXElementName('DsDateInput'), From e14f727cc970b4f1c591b2140ed302a9f7e903fb Mon Sep 17 00:00:00 2001 From: Micky Date: Thu, 23 Apr 2026 12:13:12 +0200 Subject: [PATCH 6/7] refactor(design-system)!: remove DsDateInput component [AR-53409] (#400) --- .changeset/remove-ds-date-input.md | 8 + .../ds-comments-drawer/comments-filters.ts | 9 +- .../comments-filters.types.ts | 4 +- .../comments-filters.unit.test.ts | 44 +-- .../comments-filter-modal.tsx | 26 +- .../ds-date-input/ds-date-input.module.scss | 247 ------------ .../ds-date-input.stories.module.scss | 29 -- .../ds-date-input/ds-date-input.stories.tsx | 339 ---------------- .../ds-date-input/ds-date-input.tsx | 310 --------------- .../ds-date-input/ds-date-input.types.ts | 135 ------- .../ds-date-input/ds-date-input.utils.ts | 256 ------------ .../src/components/ds-date-input/index.ts | 12 - .../ds-form-control/ds-form-control.tsx | 3 - .../ds-form-control-date-input.stories.tsx | 373 ------------------ packages/design-system/src/index.ts | 1 - packages/design-system/vitest.config.ts | 11 +- .../src/__tests__/no-deprecated.test.ts | 57 ++- packages/eslint-plugin/src/index.ts | 11 +- 18 files changed, 81 insertions(+), 1794 deletions(-) create mode 100644 .changeset/remove-ds-date-input.md delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.module.scss delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.stories.module.scss delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.stories.tsx delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.tsx delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.types.ts delete mode 100644 packages/design-system/src/components/ds-date-input/ds-date-input.utils.ts delete mode 100644 packages/design-system/src/components/ds-date-input/index.ts delete mode 100644 packages/design-system/src/components/ds-form-control/stories/ds-form-control-date-input.stories.tsx diff --git a/.changeset/remove-ds-date-input.md b/.changeset/remove-ds-date-input.md new file mode 100644 index 000000000..404ce1fee --- /dev/null +++ b/.changeset/remove-ds-date-input.md @@ -0,0 +1,8 @@ +--- +'@drivenets/design-system': major +'@drivenets/eslint-plugin-design-system': major +--- + +Remove deprecated `DsDateInput`, `DsFormControl.DateInput` components. +Remove deprecation rules for `DsDateInput`. +Replace `DsDateInput` with `DsDatePicker` or `DsDateRangePicker` instead. diff --git a/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts b/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts index f605955c0..ac0654ad6 100644 --- a/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts +++ b/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts @@ -3,6 +3,10 @@ import type { FilterTag, CommentsFilterState } from './comments-filters.types'; export type { FilterTag, CommentsFilterState } from './comments-filters.types'; +const formatFilterDateForTag = (d: Date): string => { + return d.toISOString().split('T')[0] ?? ''; +}; + export const initialFilterState: CommentsFilterState = { authors: [], labels: [], @@ -42,6 +46,7 @@ export const applyFilters = ( if (filters.dateFrom) { const fromDate = new Date(filters.dateFrom); + fromDate.setHours(0, 0, 0, 0); if (comment.createdAt < fromDate) { return false; } @@ -89,8 +94,8 @@ export const filtersToTags = (filters: CommentsFilterState, authorMap: Map = {}): CommentData => ({ id: '1', numericId: 1, author: makeAuthor('user-1', 'Alice'), - createdAt: new Date('2025-06-15'), + createdAt: new Date(2025, 5, 15), isResolved: false, messages: [], ...overrides, @@ -125,11 +125,11 @@ describe('comments-filters', () => { it('should filter by dateFrom', () => { const comments = [ - makeComment({ id: '1', createdAt: new Date('2025-01-01') }), - makeComment({ id: '2', createdAt: new Date('2025-06-15') }), - makeComment({ id: '3', createdAt: new Date('2025-12-31') }), + makeComment({ id: '1', createdAt: new Date(2025, 0, 1) }), + makeComment({ id: '2', createdAt: new Date(2025, 5, 15) }), + makeComment({ id: '3', createdAt: new Date(2025, 11, 31) }), ]; - const filters: CommentsFilterState = { ...emptyFilters, dateFrom: '2025-06-01' }; + const filters: CommentsFilterState = { ...emptyFilters, dateFrom: new Date(2025, 5, 1) }; const result = applyFilters(comments, filters, neverActionRequired); @@ -139,11 +139,11 @@ describe('comments-filters', () => { it('should filter by dateTo (inclusive end of day)', () => { const comments = [ - makeComment({ id: '1', createdAt: new Date('2025-01-01') }), - makeComment({ id: '2', createdAt: new Date('2025-06-15T23:59:59.999') }), - makeComment({ id: '3', createdAt: new Date('2025-12-31') }), + makeComment({ id: '1', createdAt: new Date(2025, 0, 1) }), + makeComment({ id: '2', createdAt: new Date(2025, 5, 15, 23, 59, 59, 999) }), + makeComment({ id: '3', createdAt: new Date(2025, 11, 31) }), ]; - const filters: CommentsFilterState = { ...emptyFilters, dateTo: '2025-06-15' }; + const filters: CommentsFilterState = { ...emptyFilters, dateTo: new Date(2025, 5, 15) }; const result = applyFilters(comments, filters, neverActionRequired); @@ -153,14 +153,14 @@ describe('comments-filters', () => { it('should filter by date range', () => { const comments = [ - makeComment({ id: '1', createdAt: new Date('2025-01-01') }), - makeComment({ id: '2', createdAt: new Date('2025-06-15') }), - makeComment({ id: '3', createdAt: new Date('2025-12-31') }), + makeComment({ id: '1', createdAt: new Date(2025, 0, 1) }), + makeComment({ id: '2', createdAt: new Date(2025, 5, 15) }), + makeComment({ id: '3', createdAt: new Date(2025, 11, 31) }), ]; const filters: CommentsFilterState = { ...emptyFilters, - dateFrom: '2025-03-01', - dateTo: '2025-09-01', + dateFrom: new Date(2025, 2, 1), + dateTo: new Date(2025, 8, 1), }; const result = applyFilters(comments, filters, neverActionRequired); @@ -319,8 +319,8 @@ describe('comments-filters', () => { it('should create date range tag with both dates', () => { const filters: CommentsFilterState = { ...emptyFilters, - dateFrom: '2025-01-01', - dateTo: '2025-12-31', + dateFrom: new Date(2025, 0, 1), + dateTo: new Date(2025, 11, 31), }; const result = filtersToTags(filters, authorMap); @@ -335,7 +335,7 @@ describe('comments-filters', () => { }); it('should create date range tag with only dateFrom', () => { - const filters: CommentsFilterState = { ...emptyFilters, dateFrom: '2025-01-01' }; + const filters: CommentsFilterState = { ...emptyFilters, dateFrom: new Date(2025, 0, 1) }; const result = filtersToTags(filters, authorMap); @@ -344,7 +344,7 @@ describe('comments-filters', () => { }); it('should create date range tag with only dateTo', () => { - const filters: CommentsFilterState = { ...emptyFilters, dateTo: '2025-12-31' }; + const filters: CommentsFilterState = { ...emptyFilters, dateTo: new Date(2025, 11, 31) }; const result = filtersToTags(filters, authorMap); @@ -382,8 +382,8 @@ describe('comments-filters', () => { const filters: CommentsFilterState = { statuses: ['resolved'], authors: ['user-1'], - dateFrom: '2025-01-01', - dateTo: '2025-12-31', + dateFrom: new Date(2025, 0, 1), + dateTo: new Date(2025, 11, 31), labels: ['bug'], }; @@ -444,8 +444,8 @@ describe('comments-filters', () => { it('should clear both dateFrom and dateTo when removing a date tag', () => { const filters: CommentsFilterState = { ...emptyFilters, - dateFrom: '2025-01-01', - dateTo: '2025-12-31', + dateFrom: new Date(2025, 0, 1), + dateTo: new Date(2025, 11, 31), }; const tag: FilterTag = { id: 'date-range', diff --git a/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx b/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx index 67ab5d9f4..277a6d1d6 100644 --- a/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +++ b/packages/design-system/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx @@ -7,7 +7,7 @@ import { DsTypography } from '../../../ds-typography'; import { DsCheckbox } from '../../../ds-checkbox'; import { DsButton } from '../../../ds-button'; import { DsIcon } from '../../../ds-icon'; -import { DsDateInput } from '../../../ds-date-input'; +import { DsDatePicker } from '../../../ds-date-picker'; export const CommentsFilterModal = ({ open, @@ -42,12 +42,12 @@ export const CommentsFilterModal = ({ }); }; - const handleDateFromChange = (value: string | undefined) => { - onFiltersChange({ ...filters, dateFrom: value }); + const handleDateFromChange = (value: Date | null) => { + onFiltersChange({ ...filters, dateFrom: value ?? undefined }); }; - const handleDateToChange = (value: string | undefined) => { - onFiltersChange({ ...filters, dateTo: value }); + const handleDateToChange = (value: Date | null) => { + onFiltersChange({ ...filters, dateTo: value ?? undefined }); }; const filterCounts = { @@ -160,10 +160,10 @@ export const CommentsFilterModal = ({ From - @@ -172,10 +172,10 @@ export const CommentsFilterModal = ({ To - diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.module.scss b/packages/design-system/src/components/ds-date-input/ds-date-input.module.scss deleted file mode 100644 index 6d6d01cc1..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.module.scss +++ /dev/null @@ -1,247 +0,0 @@ -@use '../../styles/shared/input'; -@use '../../styles/shared/dropdown'; - -.dateInputControl { - @include input.base-input-container; - @include input.input-container-states; - max-height: input.$input-text-height-default; - justify-content: space-between; - padding: var(--xs) var(--sm); - gap: var(--3xs); - - border-color: var(--form-control-border-color, var(--border)); - - &:hover:not(.disabled) { - border-color: var(--form-control-border-color, var(--color-border-hover)); - } - - &:focus-within { - border-color: var(--form-control-border-color, var(--color-border-action)); - } - - &.disabled { - color: var(--background-deselected); - background: var(--background-secondary); - border-color: var(--border-disabled); - cursor: not-allowed; - - .input { - color: var(--font-disabled); - } - - .dateInputButton { - color: var(--background-disable); - cursor: not-allowed; - } - } - - &.readOnly { - background: var(--background-secondary); - cursor: default; - } -} - -.input { - @include input.base-input; - min-width: 0; - flex: 1; - padding: 0; -} - -.dateInputButton { - @include input.icon-button; - display: flex; - align-items: center; - cursor: pointer; - padding: 0; - transition: color 0.2s ease; - - &:hover:not(:disabled) { - color: var(--background-action-hover); - } - - &:disabled { - color: var(--background-disable); - cursor: not-allowed; - } -} - -.positioner { - @include dropdown.dropdown-viewport; -} - -.content { - @include dropdown.dropdown-content; - padding: var(--standard); - min-width: fit-content; -} - -.viewControl { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--xs); -} - -.viewTrigger { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - cursor: pointer; - padding: var(--2xs) var(--xs); - border-radius: var(--2xs); - color: var(--font-main); - transition: background-color 0.2s; - - &:hover { - background: var(--color-background-hover); - } -} - -.prevTrigger, -.nextTrigger { - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - cursor: pointer; - padding: var(--2xs); - border-radius: var(--2xs); - color: var(--font-secondary); - transition: all 0.2s; - - &:hover { - background: var(--color-background-hover); - color: var(--font-main); - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } -} - -.calendarView { - display: flex; - flex-direction: column; - gap: var(--xs); -} - -.table { - width: 100%; - border-collapse: collapse; - - .tableBody .tableRow .tableCellTrigger:focus-visible { - .tableCellTriggerValue { - outline: 2px solid var(--background-action-hover); - border-radius: var(--2xs); - border-width: 0; - } - } -} - -.tableHead { - border-bottom: 1px solid var(--border); -} - -.tableHeader { - text-align: center; - padding: var(--2xs); - color: var(--font-secondary); - font-weight: var(--font-weight-medium); -} - -.tableRow { - &:first-child { - .tableCellTrigger { - margin-top: var(--xs); - } - } -} - -.tableCellTrigger { - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - user-select: none; - -webkit-tap-highlight-color: transparent; - outline: none; - - .tableCellTriggerValue { - display: flex; - align-items: center; - justify-content: center; - width: var(--xl); - height: var(--xl); - margin: var(--3xs); - color: var(--font-main); - border-radius: var(--xs); - } - - &:hover:not([data-disabled], [data-selected], [data-in-range], [data-in-hover-range]) - .tableCellTriggerValue { - outline: 1px solid var(--border); - background: var(--background-action-hover-weak); - } - - &[data-outside-range] .tableCellTriggerValue { - color: var(--font-disabled); - } - - &[data-selected] .tableCellTriggerValue { - background: var(--background-primary); - color: var(--font-on-action); - } - - &[data-in-range]:not([data-selected]) { - position: relative; - - .tableCellTriggerValue { - background: var(--background-action-secondary-hover); - } - } - - &[data-today]:not([data-selected], [data-in-range]):hover .tableCellTriggerValue { - border-radius: var(--xs); - } - - &[data-today]:not([data-selected]) .tableCellTriggerValue { - position: relative; - - &::after { - content: ''; - position: absolute; - width: 75%; - bottom: 4px; - border-bottom: 1px solid var(--background-brand); - } - } - - &[data-today][data-selected] .tableCellTriggerValue { - position: relative; - - &::after { - content: ''; - position: absolute; - width: 75%; - bottom: 4px; - border-bottom: 1px solid var(--background); - } - } - - &[data-disabled] .tableCellTriggerValue { - color: var(--font-disabled); - cursor: not-allowed; - opacity: 0.5; - } - - &[data-unavailable] .tableCellTriggerValue { - text-decoration: line-through; - opacity: 0.4; - } -} diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.stories.module.scss b/packages/design-system/src/components/ds-date-input/ds-date-input.stories.module.scss deleted file mode 100644 index bbb23cef7..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.stories.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -.containerSingle { - width: 320px; -} - -.containerRange { - width: 420px; -} - -.helperText { - font-size: 12px; - margin-top: 8px; - color: var(--font-secondary); -} - -.errorText { - font-size: 12px; - margin-top: 8px; - color: var(--color-error); -} - -.infoContainer { - font-size: 12px; - margin-top: 8px; - color: var(--font-secondary); -} - -.buttonContainer { - margin-bottom: 16px; -} diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.stories.tsx b/packages/design-system/src/components/ds-date-input/ds-date-input.stories.tsx deleted file mode 100644 index 21a72e707..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.stories.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { useState } from 'react'; -import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; -import MockDate from 'mockdate'; -import DsDateInput from './ds-date-input'; -import type { DsDateInputProps } from './ds-date-input.types'; -import styles from './ds-date-input.stories.module.scss'; - -// Mock system time to January 15, 2026 for consistent test dates -const MOCK_DATE = new Date('2026-01-15T12:00:00'); - -/** - * @deprecated This component is deprecated. Use `DsDatePicker` or `DsDateRangePicker` instead. - * @see {@link ../ds-date-picker/ds-date-picker.stories} for single date selection examples. - * @see {@link ../ds-date-range-picker/ds-date-range-picker.stories} for date range selection examples. - */ -const meta: Meta = { - title: 'Components/DateInput (Deprecated)', - component: DsDateInput, - parameters: { - layout: 'centered', - docs: { - description: { - component: - '**Deprecated**: This component is deprecated. Please use `DsDatePicker` for single date selection or `DsDateRangePicker` for date range selection instead.', - }, - }, - }, - tags: ['deprecated'], - beforeEach: () => { - // We use mockdate here and not vi.useFakeTimers() because the latter is not compatible with Storybook. - // See https://github.com/storybookjs/storybook/issues/31400#issuecomment-2943382690 for more details. - MockDate.set(MOCK_DATE); - - return () => { - MockDate.reset(); - }; - }, -}; - -export default meta; - -type Story = StoryObj; - -/** - * Basic single date input with default configuration - */ -export const Default: Story = { - render: function Render() { - const [value, setValue] = useState(); - - return ; - }, - play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY'); - const calendarButton = canvas.getByRole('button', { name: /open calendar/i }); - - // 1. Open calendar and select a date (01/15/2026) - await userEvent.click(calendarButton); - const jan15Button = screen.getByRole('button', { name: /Choose.*January 15, 2026/i }); - await userEvent.click(jan15Button); - - // Verify input shows selected date - await expect(input).toHaveValue('01/15/2026'); - - // 2. Open calendar again and verify it shows the new date as selected - await userEvent.click(calendarButton); - const initialSelectedCell = screen.queryByRole('gridcell', { selected: true }); - await expect(initialSelectedCell).toBeInTheDocument(); - await expect(initialSelectedCell).toHaveAttribute('data-value', '2026-01-15'); - - // 3. Type a different date into the input (01/16/2026) - await userEvent.clear(input); - await userEvent.type(input, '01/16/2026'); - - // Verify input updated - await expect(input).toHaveValue('01/16/2026'); - - // 4. Verify calendar shows the new date as selected - const selectedCell = screen.queryByRole('gridcell', { selected: true }); - await expect(selectedCell).toBeInTheDocument(); - await expect(selectedCell).toHaveAttribute('data-value', '2026-01-16'); - - // 5. Select another date (01/17/2026) - const jan17Button = screen.getByRole('button', { name: /Choose.*January 17, 2026/i }); - await userEvent.click(jan17Button); - - // 6. Verify input text updated - await expect(input).toHaveValue('01/17/2026'); - - // 7. Click clear button and verify input is cleared - const clearButton = canvas.getByRole('button', { name: /clear date/i }); - await userEvent.click(clearButton); - - await expect(input).toHaveValue(''); - - // 8. Verify clear button is gone after clearing - const clearButtonAfter = canvas.queryByRole('button', { name: /clear date/i }); - await expect(clearButtonAfter).not.toBeInTheDocument(); - - // Exit the calendar display - await userEvent.keyboard('{Escape}'); - }, -}; - -/** - * Date range input - unified input field for start and end dates - */ -export const RangeMode: Story = { - render: function Render() { - const [value, setValue] = useState<[string, string]>(); - - return ; - }, - play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY - MM/DD/YYYY'); - const calendarButton = canvas.getByRole('button', { name: /open calendar/i }); - - // 1. Open calendar and select range [01/15/2026 - 01/17/2026] - await userEvent.click(calendarButton); - - await waitFor(() => { - return expect(screen.getByRole('application', { name: 'calendar' })).toBeVisible(); - }); - - // Click start date (01/15/2026) - const jan15Button = screen.getByRole('button', { name: /January 15, 2026/i }); - await userEvent.click(jan15Button); - - // Click end date (01/17/2026) - const jan17Button = screen.getByRole('button', { name: /January 17, 2026/i }); - await userEvent.click(jan17Button); - - // 2. Check calendar shows the selection correctly (both dates marked as selected) - const allCells = screen.queryAllByRole('gridcell'); - const jan15Cell = allCells.find((cell) => cell.getAttribute('data-value') === '2026-01-15'); - const jan17Cell = allCells.find((cell) => cell.getAttribute('data-value') === '2026-01-17'); - - await expect(jan15Cell).toBeInTheDocument(); - await expect(jan17Cell).toBeInTheDocument(); - await expect(jan15Cell).toHaveAttribute('aria-selected', 'true'); - await expect(jan17Cell).toHaveAttribute('aria-selected', 'true'); - - // 3. Check input text is updated correctly - await expect(input).toHaveValue('01/15/2026 - 01/17/2026'); - - // 4. Type a new range [01/14/2026 - 01/18/2026] - await userEvent.clear(input); - await userEvent.type(input, '01/14/2026 - 01/18/2026'); - await userEvent.click(input); - await userEvent.type(input, '{Escape}'); // Trigger parsing. - - // Verify input updated - await expect(input).toHaveValue('01/14/2026 - 01/18/2026'); - - // 5. Check calendar is updated correctly - await userEvent.click(calendarButton); - - // Check the new selected dates - const jan14Cell = allCells.find((cell) => cell.getAttribute('data-value') === '2026-01-14'); - const jan18Cell = allCells.find((cell) => cell.getAttribute('data-value') === '2026-01-18'); - - await expect(jan14Cell).toBeInTheDocument(); - await expect(jan18Cell).toBeInTheDocument(); - await expect(jan14Cell).toHaveAttribute('aria-selected', 'true'); - await expect(jan18Cell).toHaveAttribute('aria-selected', 'true'); - - // 6. Click clear button and verify input is cleared - const clearButton = canvas.getByRole('button', { name: /clear date/i }); - await userEvent.click(clearButton); - - await expect(input).toHaveValue(''); - - // Verify clear button is gone after clearing - const clearButtonAfter = canvas.queryByRole('button', { name: /clear date/i }); - await expect(clearButtonAfter).not.toBeInTheDocument(); - - // Exit the calendar display - await userEvent.keyboard('{Escape}'); - }, -}; - -/** - * Single date input with pre-filled value - */ -export const WithDefaultValue: Story = { - args: { - className: styles.containerSingle, - defaultValue: '2024-12-25', - }, -}; - -/** - * Range input with pre-filled values - */ -export const RangeWithDefaultValue: Story = { - args: { - className: styles.containerRange, - defaultValue: ['2024-12-01', '2024-12-31'], - range: true, - }, -}; - -/** - * Date input with min and max constraints - */ -export const WithMinMax: Story = { - render: function Render() { - const [value, setValue] = useState(); - const today = new Date(); - const minDate = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().split('T')[0]; - const maxDate = new Date(today.getFullYear(), today.getMonth() + 3, 0).toISOString().split('T')[0]; - - return ( -
- -

Allowed: Current month to next 3 months only

-
- ); - }, -}; - -/** - * Disabled date input - */ -export const Disabled: Story = { - args: { - className: styles.containerSingle, - value: '2024-12-25', - disabled: true, - }, -}; - -/** - * Read-only date input - */ -export const ReadOnly: Story = { - args: { - className: styles.containerSingle, - value: '2024-12-25', - readOnly: true, - }, -}; - -/** - * Date input with hidden clear button - */ -export const HideClearButton: Story = { - render: function Render() { - const [value, setValue] = useState(); - - return ( - - ); - }, - play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { - const canvas = within(canvasElement); - const calendarButton = canvas.getByRole('button', { name: /open calendar/i }); - - // Select a date - await userEvent.click(calendarButton); - const jan15Button = screen.getByRole('button', { name: /Choose.*January 15, 2026/i }); - await userEvent.click(jan15Button); - - // Verify the date is selected - const input = canvas.getByPlaceholderText('MM/DD/YYYY'); - await expect(input).toHaveValue('01/15/2026'); - - // Verify the clear button is not rendered - const clearButton = canvas.queryByRole('button', { name: /clear date/i }); - await expect(clearButton).not.toBeInTheDocument(); - }, -}; - -/** - * Controlled open state - */ -export const ControlledOpen: Story = { - render: function Render() { - const [value, setValue] = useState(); - const [open, setOpen] = useState(false); - - return ( -
-
- -
- -
- ); - }, -}; - -/** - * Uncontrolled with defaultValue - */ -export const Uncontrolled: Story = { - render: function Render() { - return ( -
- -
- ); - }, -}; - -/** - * Manual typing demonstration - */ -export const ManualTyping: Story = { - render: function Render() { - const [value, setValue] = useState<[string, string]>(); - - return ( -
- -
-

Try typing: "12/01/2024 - 12/31/2024"

-

Validation happens on blur

-

Invalid format resets to empty

-

Current value: {JSON.stringify(value)}

-
-
- ); - }, -}; diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.tsx b/packages/design-system/src/components/ds-date-input/ds-date-input.tsx deleted file mode 100644 index 39d510812..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import { type ChangeEvent, Fragment, useRef, useState } from 'react'; -import { DatePicker } from '@ark-ui/react/date-picker'; -import { Portal } from '@ark-ui/react/portal'; -import classNames from 'classnames'; -import type { DsDateInputProps } from './ds-date-input.types'; -import { - dateValuesToStrings, - formatDateInputValue, - isoStringToDateValue, - parseInputText, - stringToDateValue, - validateDateString, -} from './ds-date-input.utils'; -import { DsIcon } from '../ds-icon'; -import { DsTypography } from '../ds-typography'; -import styles from './ds-date-input.module.scss'; - -/** - * @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. - * @see {@link ../ds-date-picker} for single date selection. - * @see {@link ../ds-date-range-picker} for date range selection. - */ -const DsDateInput = ({ - range = false, - placeholder, - style, - className, - disablePortal = false, - hideClearButton = false, - onValueChange, - onOpenChange, - value: _value, - defaultValue: _defaultValue, - min: _min, - max: _max, - disabled, - readOnly, - id, - ...datePickerProps -}: DsDateInputProps) => { - const min = _min ? isoStringToDateValue(_min) : undefined; - const max = _max ? isoStringToDateValue(_max) : undefined; - - // Convert public API (string | [string, string]) to internal API (DateValue[]) - const value = _value ? stringToDateValue(_value) : []; - const defaultValue = _defaultValue ? stringToDateValue(_defaultValue) : []; - - // Local state for manual text editing - const [isFocused, setIsFocused] = useState(false); - const [isOpen, setIsOpen] = useState(false); - - const prevValue = useRef(null); - const inputRef = useRef(null); - - // Reformat the input value on every value change - // We don't format when the input is focused since the date value might get updated while the user is typing - // but we don't want to reformat the input text for him while he's typing. - if (!isFocused && prevValue.current !== JSON.stringify(value)) { - prevValue.current = JSON.stringify(value); - - if (inputRef.current) { - inputRef.current.value = formatDateInputValue(value, range); - } - } - - const clear = () => { - if (inputRef.current) { - inputRef.current.value = ''; - } - - onValueChange?.(undefined); - }; - - const handleInputChange = (e: ChangeEvent) => { - const text = e.target.value; - - // Try to parse and validate as the user types - const validation = validateDateString({ text, range, min, max }); - - if (validation.isValid) { - const parsedValue = parseInputText(text, range); - onValueChange?.(dateValuesToStrings(parsedValue, range) as never); - } - }; - - const handleBlur = () => { - setIsFocused(false); - - const text = inputRef.current?.value || ''; - const validation = validateDateString({ text, range, min, max }); - - // Clear invalid input on blur - if (!validation.isValid) { - clear(); - return; - } - - const parsedValue = parseInputText(text, range); - - onValueChange?.(dateValuesToStrings(parsedValue, range) as never); - }; - - const showClearButton = - !hideClearButton && !disabled && !readOnly && !!(value.length > 0 ? value : defaultValue).length; - const Wrapper = disablePortal ? Fragment : Portal; - - return ( - { - onValueChange?.(dateValuesToStrings(details.value, range) as never); - }} - selectionMode={range ? 'range' : 'single'} - positioning={{ placement: 'bottom-start', gutter: 4 }} - closeOnSelect={!range} - min={min} - max={max} - disabled={disabled} - readOnly={readOnly} - open={isOpen} - onOpenChange={(details) => { - // Don't close the calendar when the user is typing - if (!isFocused) { - setIsOpen(details.open); - onOpenChange?.(details.open); - } - }} - {...datePickerProps} - > - - - - - - 0 ? value : defaultValue, range)} - onChange={handleInputChange} - onBlur={handleBlur} - onFocus={() => setIsFocused(true)} - disabled={disabled} - readOnly={readOnly} - /> - - {showClearButton && ( - - )} - - - - - - -
- - - -
-
-
-
-
- ); -}; - -/** - * Calendar view control with navigation buttons - */ -const CalendarViewControl = () => ( - - - - - - - - - - - -); - -/** - * Day view - displays calendar grid with days of the month - */ -const DayView = () => ( - - - {(datePicker) => ( - - - - {datePicker.weekDays.map((weekDay, id) => ( - - {weekDay.short} - - ))} - - - - {datePicker.weeks.map((week, weekId) => ( - - {week.map((day, dayId) => ( - - - - {day.day} - - - - ))} - - ))} - - - )} - - -); - -/** - * Month view - displays grid with months of the year - */ -const MonthView = () => ( - - - {(datePicker) => ( - - - {datePicker.getMonthsGrid({ columns: 4, format: 'short' }).map((months, rowId) => ( - - {months.map((month, monthId) => ( - - - - {month.label} - - - - ))} - - ))} - - - )} - - -); - -/** - * Year view - displays grid with years in a decade - */ -const YearView = () => ( - - - {(datePicker) => ( - - - {datePicker.getYearsGrid({ columns: 4 }).map((years, rowId) => ( - - {years.map((year, yearId) => ( - - - - {year.label} - - - - ))} - - ))} - - - )} - - -); - -export default DsDateInput; diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.types.ts b/packages/design-system/src/components/ds-date-input/ds-date-input.types.ts deleted file mode 100644 index 4abda87f8..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.types.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { type DatePickerRootProps } from '@ark-ui/react/date-picker'; - -/** - * Range-specific props with discriminated union for type safety - */ -type DsDateInputRangeProps = - | { - /** - * Whether to enable range selection - * @default false - */ - range?: false; - - /** - * Controlled value as ISO date string (YYYY-MM-DD) - * @example '2024-12-25' - */ - value?: string; - - /** - * Default value for uncontrolled mode - * @example '2024-12-25' - */ - defaultValue?: string; - - /** - * Callback when the value changes - * @param value - ISO date string (YYYY-MM-DD) or undefined when empty - * @example '2024-12-25' - */ - onValueChange?: (value: string | undefined) => void; - } - | { - /** - * Whether to enable range selection - */ - range: true; - - /** - * Controlled value as ISO date string tuple (YYYY-MM-DD) - * @example ['2024-12-01', '2024-12-31'] - */ - value?: [string, string]; - - /** - * Default value for uncontrolled mode - * @example ['2024-12-01', '2024-12-31'] - */ - defaultValue?: [string, string]; - - /** - * Callback when the value changes - * @param value - ISO date string tuple (YYYY-MM-DD) or undefined when empty - * @example ['2024-12-01', '2024-12-31'] - */ - onValueChange?: (value: [string, string] | undefined) => void; - }; - -/** - * Base props shared across all modes - */ -type DsDateInputBaseProps = Pick< - DatePickerRootProps, - 'disabled' | 'readOnly' | 'id' | 'open' | 'name' | 'style' -> & { - /** - * Unique identifier for the input field - */ - id?: string; - /** - * HTML name attribute for the input field, used in form submissions - */ - name?: string; - /** - * Whether the input is disabled and cannot be interacted with - * @default false - */ - disabled?: boolean; - /** - * Whether the input is read-only. The field is focusable but the value cannot - * be changed. - * @default false - */ - readOnly?: boolean; - /** - * Whether the calendar popover is open (controlled). Pair with `onOpenChange`. - */ - open?: boolean; - /** - * Callback when the open state changes - * @param open - Whether the calendar is open - */ - onOpenChange?: (open: boolean) => void; - /** - * Minimum allowed date as ISO string (YYYY-MM-DD) - * @example '2024-01-01' - */ - min?: string; - - /** - * Maximum allowed date as ISO string (YYYY-MM-DD) - * @example '2024-12-31' - */ - max?: string; - - /** - * Placeholder text for the input - * @default 'MM/DD/YYYY' for single date, 'MM/DD/YYYY - MM/DD/YYYY' for range - */ - placeholder?: string; - - /** - * Custom class name for the root container - */ - className?: string; - - /** - * Whether to disable the portal for the calendar content - * @default false - */ - disablePortal?: boolean; - - /** - * Whether to hide the clear button - * @default false - */ - hideClearButton?: boolean; -}; - -/** - * @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. - * @see {@link ../ds-date-picker} for single date selection. - * @see {@link ../ds-date-range-picker} for date range selection. - */ -export type DsDateInputProps = DsDateInputBaseProps & DsDateInputRangeProps; diff --git a/packages/design-system/src/components/ds-date-input/ds-date-input.utils.ts b/packages/design-system/src/components/ds-date-input/ds-date-input.utils.ts deleted file mode 100644 index c66685fda..000000000 --- a/packages/design-system/src/components/ds-date-input/ds-date-input.utils.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { type DateValue } from '@ark-ui/react/date-picker'; -import { CalendarDate, parseDate } from '@internationalized/date'; - -/** - * Validation result for date input - */ -interface DateInputValidation { - isValid: boolean; - error?: string; -} - -/** - * Parsed date range result - */ -interface ParsedDateRange { - start: string | null; - end: string | null; - isValid: boolean; -} - -/** - * Convert ISO date string to DateValue - */ -export const isoStringToDateValue = (isoDate: string): DateValue | undefined => { - try { - return parseDate(isoDate); - } catch { - return undefined; - } -}; - -/** - * Convert string or tuple to DateValue array (for internal use with Ark UI) - */ -export const stringToDateValue = (dates: string | [string, string]): DateValue[] => { - const dateArray = Array.isArray(dates) ? dates : [dates]; - - return dateArray.map(isoStringToDateValue).filter((d) => !!d); -}; - -/** - * Convert DateValue array to string or tuple (for public API) - */ -export const dateValuesToStrings = (dates: DateValue[], range: boolean): string | [string, string] => { - const values = dates.map((date) => date.toString()); - - if (range) { - return [values[0] ?? '', values[1] ?? '']; - } - - return values[0] ?? ''; -}; - -/** - * Format a DateValue to string (MM/DD/YYYY) - */ -const formatDateValue = (date: DateValue): string => { - const paddedMonth = String(date.month).padStart(2, '0'); - const paddedDay = String(date.day).padStart(2, '0'); - - return `${paddedMonth}/${paddedDay}/${String(date.year)}`; -}; - -/** - * Format an array of DateValues to a unified string - * Single: "12/25/2024" - * Range: "12/01/2024 - 12/31/2024" - */ -export const formatDateInputValue = (value: DateValue[], range: boolean): string => { - if (value.length === 0) { - return ''; - } - const newValue = value.map(formatDateValue); - - return range ? newValue.join(' - ') : (newValue[0] ?? ''); -}; - -/** - * Parse a date string (MM/DD/YYYY) to DateValue - */ -const parseDateString = (dateStr: string): DateValue | null => { - if (!dateStr || !dateStr.trim()) { - return null; - } - - // Try MM/DD/YYYY format - const match = dateStr.trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); - if (!match) { - return null; - } - - const [, month, day, year] = match; - - if (!month || !day || !year) { - return null; - } - - const monthNum = parseInt(month, 10); - const dayNum = parseInt(day, 10); - const yearNum = parseInt(year, 10); - - // Use native Date for proper validation (handles leap years, month lengths, etc.) - // Note: Date months are 0-indexed, so subtract 1 - const date = new Date(yearNum, monthNum - 1, dayNum); - - // Check if the date is valid and matches what we parsed - // (e.g., 02/31/2024 would roll over to 03/02/2024, so we detect that) - if (date.getFullYear() !== yearNum || date.getMonth() !== monthNum - 1 || date.getDate() !== dayNum) { - return null; - } - - return new CalendarDate(yearNum, monthNum, dayNum); -}; - -/** - * Parse a range input string (MM/DD/YYYY - MM/DD/YYYY) - */ -const parseRangeInput = (text: string): ParsedDateRange => { - if (!text.trim()) { - return { start: null, end: null, isValid: true }; // Empty is valid - } - - // Check if it contains a dash separator - if (!text.includes('-')) { - // Single date format, treat as incomplete range - const date = parseDateString(text.trim()); - return { - start: date ? formatDateValue(date) : null, - end: null, - isValid: false, // Incomplete range - }; - } - - // Split by dash - const parts = text.split('-').map((p) => p.trim()); - if (parts.length !== 2) { - return { start: null, end: null, isValid: false }; - } - - const [startStr, endStr] = parts; - - if (!startStr || !endStr) { - return { start: null, end: null, isValid: false }; - } - - const startDate = parseDateString(startStr); - const endDate = parseDateString(endStr); - - // Both must be valid dates - if (!startDate || !endDate) { - return { - start: startDate ? formatDateValue(startDate) : null, - end: endDate ? formatDateValue(endDate) : null, - isValid: false, - }; - } - - return { - start: formatDateValue(startDate), - end: formatDateValue(endDate), - isValid: true, - }; -}; - -/** - * Parse input text based on range flag - */ -export const parseInputText = (text: string, range: boolean): DateValue[] => { - if (!range) { - const date = parseDateString(text); - return date ? [date] : []; - } - - // Range mode - const parsed = parseRangeInput(text); - if (!parsed.isValid || !parsed.start || !parsed.end) { - return []; - } - - const startDate = parseDateString(parsed.start); - const endDate = parseDateString(parsed.end); - - if (!startDate || !endDate) { - return []; - } - - return [startDate, endDate]; -}; - -/** - * Validate date input - */ -export const validateDateString = ({ - text, - range, - min, - max, -}: { - text: string; - range: boolean; - min?: DateValue; - max?: DateValue; -}): DateInputValidation => { - if (!text.trim()) { - return { isValid: true }; // Empty is valid (let required validation handle it) - } - - if (!range) { - const date = parseDateString(text); - if (!date) { - return { isValid: false, error: 'Invalid date format. Use MM/DD/YYYY' }; - } - - // Check min/max - if (min && date.compare(min) < 0) { - return { isValid: false, error: 'Date is before minimum allowed date' }; - } - if (max && date.compare(max) > 0) { - return { isValid: false, error: 'Date is after maximum allowed date' }; - } - - return { isValid: true }; - } - - // Range mode validation - const parsed = parseRangeInput(text); - if (!parsed.isValid) { - return { isValid: false, error: 'Invalid date range format. Use MM/DD/YYYY - MM/DD/YYYY' }; - } - - if (!parsed.start || !parsed.end) { - return { isValid: false, error: 'Both start and end dates are required' }; - } - - const startDate = parseDateString(parsed.start); - const endDate = parseDateString(parsed.end); - - if (!startDate || !endDate) { - return { isValid: false, error: 'Invalid date format. Use MM/DD/YYYY' }; - } - - // Validate start <= end - if (startDate.compare(endDate) > 0) { - return { isValid: false, error: 'Start date must be before or equal to end date' }; - } - - // Check min/max - if (min && startDate.compare(min) < 0) { - return { isValid: false, error: 'Start date is before minimum allowed date' }; - } - if (max && endDate.compare(max) > 0) { - return { isValid: false, error: 'End date is after maximum allowed date' }; - } - - return { isValid: true }; -}; diff --git a/packages/design-system/src/components/ds-date-input/index.ts b/packages/design-system/src/components/ds-date-input/index.ts deleted file mode 100644 index 12b09c0c5..000000000 --- a/packages/design-system/src/components/ds-date-input/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. - * @see {@link ../ds-date-picker} for single date selection. - * @see {@link ../ds-date-range-picker} for date range selection. - */ -export { default as DsDateInput } from './ds-date-input'; -/** - * @deprecated These types are deprecated. Use DsDatePicker or DsDateRangePicker types instead. - * @see {@link ../ds-date-picker} for single date selection. - * @see {@link ../ds-date-range-picker} for date range selection. - */ -export type { DsDateInputProps } from './ds-date-input.types'; diff --git a/packages/design-system/src/components/ds-form-control/ds-form-control.tsx b/packages/design-system/src/components/ds-form-control/ds-form-control.tsx index 7b9db466b..93d2bf000 100644 --- a/packages/design-system/src/components/ds-form-control/ds-form-control.tsx +++ b/packages/design-system/src/components/ds-form-control/ds-form-control.tsx @@ -9,7 +9,6 @@ import { DsNumberInput } from '../ds-number-input'; import { DsPasswordInput } from '../ds-password-input'; import type { DsFormControlDescriptionProps, DsFormControlProps } from './ds-form-control.types'; import styles from './ds-form-control.module.scss'; -import { DsDateInput } from '../ds-date-input'; import { DsDatePicker } from '../ds-date-picker'; import { DsTimePicker } from '../ds-time-picker'; @@ -99,8 +98,6 @@ const DsFormControl = ({ DsFormControl.TextInput = controlify(DsTextInput); DsFormControl.NumberInput = controlify(DsNumberInput); DsFormControl.PasswordInput = controlify(DsPasswordInput); -/** @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. */ -DsFormControl.DateInput = controlify(DsDateInput); DsFormControl.DatePicker = controlify(DsDatePicker); DsFormControl.TimePicker = controlify(DsTimePicker); DsFormControl.Textarea = controlify(DsTextarea); diff --git a/packages/design-system/src/components/ds-form-control/stories/ds-form-control-date-input.stories.tsx b/packages/design-system/src/components/ds-form-control/stories/ds-form-control-date-input.stories.tsx deleted file mode 100644 index 2d75b8cc4..000000000 --- a/packages/design-system/src/components/ds-form-control/stories/ds-form-control-date-input.stories.tsx +++ /dev/null @@ -1,373 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { useState } from 'react'; -import { expect, userEvent, waitFor, within } from 'storybook/test'; -import { controlStatuses } from '../ds-form-control.types'; -import DsFormControl from '../ds-form-control'; -import { DefaultDescription } from './ds-form-control-stories-shared'; -import styles from './ds-form-control.stories.module.scss'; -import { DsIcon } from '../../ds-icon'; - -const meta: Meta = { - title: 'Components/FormControl/DateInput (Deprecated)', - component: DsFormControl.DateInput, - parameters: { - layout: 'centered', - docs: { - description: { - component: - '**Deprecated**: DsFormControl.DateInput is deprecated. Please use `DsFormControl.DsDatePicker` for single date selection or `DsFormControl.DsDateRangePicker` for date range selection instead.', - }, - }, - }, - tags: ['deprecated'], - argTypes: { - status: { - control: { type: 'select' }, - options: controlStatuses, - description: 'Form control color status', - table: { - defaultValue: { - summary: controlStatuses[0], - }, - }, - }, - label: { - control: 'text', - description: 'Label for the form control', - }, - required: { - control: 'boolean', - description: 'Indicates if the field is required', - }, - message: { - control: 'text', - description: 'Message to display below the form control', - }, - messageIcon: { - control: 'text', - description: 'Icon to display in the message', - }, - className: { - control: 'text', - description: 'Additional CSS class names', - }, - style: { - control: 'object', - description: 'Additional styles to apply to the component', - }, - }, -}; - -export default meta; -type Story = StoryObj; - -const sanityCheckSingle = async (canvasElement: HTMLElement) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY'); - - // Test typing a date - await userEvent.click(input); - await userEvent.type(input, '12/25/2024'); - await waitFor(async () => { - await expect(input).toHaveValue('12/25/2024'); - }); - - // Test clearing the input - await userEvent.clear(input); - await expect(input).toHaveValue(''); -}; - -const sanityCheckRange = async (canvasElement: HTMLElement) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY - MM/DD/YYYY'); - - // Test typing a date range - await userEvent.click(input); - await userEvent.type(input, '12/01/2024 - 12/31/2024'); - await waitFor(async () => { - await expect(input).toHaveValue('12/01/2024 - 12/31/2024'); - }); -}; - -const checkDisabled = async (canvasElement: HTMLElement) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY'); - const calendarButton = canvas.getByRole('button', { name: /open calendar/i }); - - // Assert that the input and button are disabled - await expect(input).toBeDisabled(); - await expect(calendarButton).toBeDisabled(); - - // Attempt to type into the disabled input - await userEvent.type(input, '12/25/2024'); - - // Assert that the input value remains unchanged - await expect(input.value).toBe(''); -}; - -export const Default: Story = { - args: { - label: 'Event Date', - required: true, - style: { width: '400px' }, - message: 'Select a date for your event', - }, - render: (args) => ( - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckSingle(canvasElement); - }, -}; - -export const WithCustomWidth: Story = { - args: { - label: 'Event Date', - required: true, - style: { width: '400px' }, - }, - render: (args) => ( - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckSingle(canvasElement); - }, -}; - -export const WithCustomStyles: Story = { - args: { - label: 'Event Date', - required: true, - style: { - width: '400px', - padding: '16px', - border: '2px solid #e0e0e0', - borderRadius: '8px', - backgroundColor: '#f9f9f9', - }, - }, - render: (args) => ( - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckSingle(canvasElement); - }, -}; - -export const WithDescription: Story = { - args: { - label: 'Event Date', - required: true, - style: { width: '400px' }, - }, - render: (args) => ( - - - - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckSingle(canvasElement); - }, -}; - -export const WithHelpIcon: Story = { - args: { - label: 'Event Date', - required: true, - style: { width: '400px' }, - slots: { - endAdornment: ( - - ), - }, - }, - render: (args) => ( - - - - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckSingle(canvasElement); - }, -}; - -export const Success: Story = { - render: function Render() { - const [value] = useState('2024-12-25'); - - return ( -
- - - - - - -
- ); - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const input = canvas.getByPlaceholderText('MM/DD/YYYY'); - await waitFor(async () => { - await expect(input).toHaveValue('12/25/2024'); - }); - }, -}; - -export const Error: Story = { - args: { - status: 'error', - label: 'Event Date', - required: true, - message: 'Date is required.', - messageIcon: 'error', - style: { width: '400px' }, - }, - render: (args) => ( - - - - - - - ), -}; - -export const Warning: Story = { - render: function Render() { - const [value] = useState('2024-12-25'); - - return ( -
- - - - - - -
- ); - }, -}; - -export const Disabled: Story = { - args: { - label: 'Event Date', - style: { width: '400px' }, - }, - render: (args) => ( - - - - - - - ), - play: async ({ canvasElement }) => { - await checkDisabled(canvasElement); - }, -}; - -export const RangeMode: Story = { - args: { - label: 'Date Range', - required: true, - message: 'Select start and end dates', - style: { width: '400px' }, - }, - render: (args) => ( - - - - ), - play: async ({ canvasElement }) => { - await sanityCheckRange(canvasElement); - }, -}; - -export const RangeWithValidation: Story = { - render: function Render() { - const [value, setValue] = useState<[string, string]>(); - const [touched, setTouched] = useState(false); - const error = touched && !value ? 'Start and end dates are required' : undefined; - - return ( -
- - { - setValue(value); - setTouched(true); - }} - range - /> - -
- ); - }, -}; - -export const WithValidation: Story = { - render: function Render() { - const [value, setValue] = useState(); - const [touched, setTouched] = useState(false); - const error = touched && !value ? 'Date is required' : undefined; - - return ( -
- - { - setValue(value); - setTouched(true); - }} - /> - -
- ); - }, -}; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index d38320fea..271762ca1 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -12,7 +12,6 @@ export * from './components/ds-comment-bubble'; export * from './components/ds-comment-card'; export * from './components/ds-comment-indicator'; export * from './components/ds-comments-drawer'; -export * from './components/ds-date-input'; export * from './components/ds-date-picker'; export * from './components/ds-date-range-picker'; export * from './components/ds-divider'; diff --git a/packages/design-system/vitest.config.ts b/packages/design-system/vitest.config.ts index 157ea3628..6d7f23734 100644 --- a/packages/design-system/vitest.config.ts +++ b/packages/design-system/vitest.config.ts @@ -13,16 +13,7 @@ export default defineConfig({ test: { coverage: { include: ['src/**/*.{ts,tsx}'], - exclude: [ - '**/stories/**', - '**/*.stories.*{ts,tsx}', - '**/.storybook/**', - '**/*.scss', - 'dist/**', - - // deprecated components - '**/ds-date-input/**', - ], + exclude: ['**/stories/**', '**/*.stories.*{ts,tsx}', '**/.storybook/**', '**/*.scss', 'dist/**'], thresholds: { lines: 90, }, diff --git a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts index 7fe3988bd..4f0983976 100644 --- a/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts +++ b/packages/eslint-plugin/src/__tests__/no-deprecated.test.ts @@ -1,5 +1,5 @@ import { RuleTester } from 'eslint'; -import plugin from '../index'; +import { expect, it } from 'vitest'; const ruleTester = new RuleTester({ languageOptions: { @@ -11,40 +11,27 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run('no-deprecated-ds-date-input', plugin.rules['no-deprecated-ds-date-input'], { - valid: ['', ''], - - invalid: [ - { - code: '', - errors: [ - { - message: `DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 13, - }, - ], - }, - ], +it('placeholder: RuleTester instantiates', () => { + expect(ruleTester).toBeInstanceOf(RuleTester); }); -ruleTester.run('no-deprecated-ds-popover-legacy', plugin.rules['no-deprecated-ds-popover-legacy'], { - valid: ['', '', ''], +// Example test for the future reference - invalid: [ - { - code: '}>Content', - errors: [ - { - message: `The DsPopover callable form (with a 'trigger' prop) is deprecated. Use the compound API: DsPopover.Root with DsPopover.Trigger and DsPopover.Panel.`, - line: 1, - endLine: 1, - column: 2, - endColumn: 11, - }, - ], - }, - ], -}); +// ruleTester.run('no-deprecated-ds-date-input', plugin.rules['no-deprecated-ds-date-input'], { +// valid: ['', ''], + +// invalid: [ +// { +// code: '', +// errors: [ +// { +// message: `DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead.`, +// line: 1, +// endLine: 1, +// column: 2, +// endColumn: 13, +// }, +// ], +// }, +// ], +// }); diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index d8d583f33..1ee6e03ad 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -3,11 +3,12 @@ import { createPlugin } from './create-plugin'; const eslintPlugin = createPlugin( '@drivenets/design-system', - { - name: 'no-deprecated-ds-date-input', - selector: JSXElementName('DsDateInput'), - message: `DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead.`, - }, + // Example test for deprecated components, for future reference + // { + // name: 'no-deprecated-ds-date-input', + // selector: JSXElementName('DsDateInput'), + // message: `DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead.`, + // }, { // Matches only the bare callable ``, not the compound `` etc. From 8824a6a10335d139f70239bd47c3fdbee53dbb49 Mon Sep 17 00:00:00 2001 From: Michal Murawski Date: Wed, 6 May 2026 16:16:19 +0200 Subject: [PATCH 7/7] chore(design-system): resolve all after rebase issues and migrate to tokens [AR-60245] --- .../__tests__/ds-button.browser.test.tsx | 12 +- .../ds-button-legacy.module.scss | 257 -------- .../ds-button-legacy.stories.tsx | 147 ----- .../ds-button-new/ds-button-new.module.scss | 604 ------------------ .../ds-button-new.stories.module.scss | 36 -- .../ds-button-new/ds-button-new.stories.tsx | 213 ------ .../ds-catalog-layout.stories.tsx | 16 +- .../ds-comment-bubble.types.ts | 2 +- .../ds-comment-card/ds-comment-card.types.ts | 6 +- .../ds-comments-drawer/comments-filters.ts | 7 +- .../comments-filters.types.ts | 8 +- .../ds-popover/ds-popover.stories.tsx | 14 +- .../__tests__/ds-select.browser.test.tsx | 18 +- .../ds-split-button/ds-split-button.figma.ts | 2 +- .../ds-split-button/ds-split-button.types.ts | 2 +- .../ds-status-badge-v2.browser.test.tsx | 2 +- .../ds-table-bulk-actions.module.scss | 16 +- .../ds-table-group-header-cell.tsx | 4 +- .../ds-table-editable-cell.tsx | 4 +- .../ds-table-editing-cell.tsx | 6 +- .../column-filter-popover.tsx | 14 +- .../ds-table-column-filters.stories.tsx | 12 +- .../ds-workspace-layout.stories.tsx | 210 +++--- .../src/stories/patterns-forms.mdx | 12 +- .../src/stories/sample-form/sample-form.tsx | 6 +- 25 files changed, 185 insertions(+), 1445 deletions(-) delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.tsx delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.module.scss delete mode 100644 packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.tsx diff --git a/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx b/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx index bb6fed8ab..eb1545a2e 100644 --- a/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx +++ b/packages/design-system/src/components/ds-button/__tests__/ds-button.browser.test.tsx @@ -207,9 +207,9 @@ describe('DsButton', () => { it('primary has a non-transparent background', async () => { await page.render( - + Light - , + , ); const el = page.getByRole('button', { name: 'Light' }).element(); @@ -219,9 +219,9 @@ describe('DsButton', () => { it('primary disabled has a non-transparent background', async () => { await page.render( - + Light - , + , ); const el = page.getByRole('button', { name: 'Light' }).element(); @@ -231,9 +231,9 @@ describe('DsButton', () => { it('secondary selected has a non-transparent background', async () => { await page.render( - + Light - , + , ); const el = page.getByRole('button', { name: 'Light' }).element(); diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss deleted file mode 100644 index 65e84c617..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +++ /dev/null @@ -1,257 +0,0 @@ -@use '../../../../styles/typography'; - -$color-primary-default: var(--action-cta1); -$color-primary-hover: var(--action-hover-light); -$color-primary-active: var(--action-active-light); -$color-primary-disabled: var(--action-disabled); - -$color-secondary-default: var(--action-cta3); -$color-secondary-hover: var(--color-dap-gray-500); -$color-secondary-disabled: var(--action-disabled); - -$color-error-default: var(--background-background-negative); -$color-error-hover: var(--utility-error); -$color-error-active: var(--utility-error); -$color-error-disabled: var(--action-disabled); - -$color-white: var(--color-dap-gray-050); -$color-neutral-6: var(--utility-disabled); -$color-neutral-4: var(--color-dap-gray-500); - -@mixin button-filled($background, $hover, $active, $disabled) { - background: $background; - color: $color-white; - - &:disabled { - color: $color-neutral-6; - background: $disabled; - } -} - -@mixin button-ghost($color, $hover, $active, $disabled) { - border: 2px solid $color; - color: $color; - - &:disabled { - color: $disabled; - border-color: $disabled; - } -} - -@mixin button-borderless($color, $hover, $active, $disabled) { - color: $color; - border: none; - - &.content { - gap: 4px; - } - - &:hover { - color: $hover; - } - - &:active { - color: $active; - } - - &:disabled { - color: $disabled; - } -} - -@mixin button-round($background, $hover, $active, $disabled) { - @include button-filled($background, $hover, $active, $disabled); - border-radius: 32px; -} - -.button { - @include typography.regular; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - white-space: nowrap; - cursor: pointer; - border: none; - padding: 0; - font: inherit; - background: none; - - &:disabled { - cursor: not-allowed; - } - - &:not(:disabled) { - cursor: pointer; - } - - .content { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - } - - &.small { - @include typography.p-base; - padding: 6px 12px; - - .content { - gap: 6px; - } - } - - &.medium { - @include typography.p-base; - @include typography.medium; - padding: 12px; - - .content { - gap: 9px; - } - } - - &.large { - @include typography.p-base; - @include typography.medium; - padding: 14px 12px; - } - - &.primary-filled { - @include button-filled( - $color-primary-default, - $color-primary-hover, - $color-primary-active, - $color-primary-disabled - ); - } - - &.primary-ghost { - @include button-ghost( - $color-primary-default, - $color-primary-hover, - $color-primary-active, - $color-neutral-4 - ); - } - - &.primary-borderless { - @include button-borderless( - $color-primary-default, - $color-primary-hover, - $color-primary-active, - $color-primary-disabled - ); - } - - &.primary-round { - @include button-round( - $color-primary-default, - $color-primary-hover, - $color-primary-active, - $color-primary-disabled - ); - } - - &.primary-dashed { - @include button-ghost( - $color-primary-default, - $color-primary-hover, - $color-primary-active, - $color-neutral-4 - ); - border-style: dashed; - } - - &.secondary-filled { - @include button-filled( - $color-secondary-default, - $color-secondary-hover, - $color-secondary-hover, - $color-secondary-disabled - ); - } - - &.secondary-ghost { - @include button-ghost( - $color-secondary-default, - $color-secondary-hover, - $color-secondary-hover, - $color-secondary-disabled - ); - } - - &.secondary-borderless { - @include button-borderless( - $color-secondary-default, - $color-secondary-hover, - $color-secondary-hover, - $color-secondary-disabled - ); - } - - &.secondary-round { - @include button-round( - $color-secondary-default, - $color-secondary-hover, - $color-secondary-hover, - $color-secondary-disabled - ); - } - - &.secondary-dashed { - @include button-ghost( - $color-secondary-default, - $color-secondary-hover, - $color-secondary-hover, - $color-secondary-disabled - ); - border-style: dashed; - } - - &.error-filled { - @include button-filled( - $color-error-default, - $color-error-hover, - $color-error-active, - $color-error-disabled - ); - } - - &.error-ghost { - @include button-ghost( - $color-error-default, - $color-error-hover, - $color-error-active, - $color-error-disabled - ); - } - - &.error-borderless { - @include button-borderless( - $color-error-default, - $color-error-hover, - $color-error-active, - $color-error-disabled - ); - } - - &.error-round { - @include button-round( - $color-error-default, - $color-error-hover, - $color-error-active, - $color-error-disabled - ); - } - - &.error-dashed { - @include button-ghost( - $color-error-default, - $color-error-hover, - $color-error-active, - $color-error-disabled - ); - border-style: dashed; - } -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.tsx b/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.tsx deleted file mode 100644 index 5f8c0bc27..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.stories.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { expect, fn, userEvent, within } from 'storybook/test'; -import classNames from 'classnames'; -import { DsIcon } from '../../../ds-icon'; -import styles from './ds-button-legacy.stories.module.scss'; -import DsButtonLegacy from './ds-button-legacy'; -import { - type ButtonSchema, - type ButtonVariant, - buttonSchemas, - buttonSizes, - buttonVariants, -} from './ds-button-legacy.types'; - -const meta: Meta = { - title: 'Components/ButtonLegacy (Deprecated)', - component: DsButtonLegacy, - parameters: { - layout: 'centered', - }, - tags: ['deprecated'], - argTypes: { - schema: { - control: { type: 'select' }, - options: buttonSchemas, - description: 'Button color schema', - table: { - defaultValue: { - summary: 'primary', - }, - }, - }, - variant: { - control: { type: 'select' }, - options: buttonVariants, - table: { - defaultValue: { - summary: 'filled', - }, - }, - }, - size: { - control: { type: 'select' }, - options: buttonSizes, - table: { - defaultValue: { - summary: 'medium', - }, - }, - }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn() }, -}; -export default meta; -type Story = StoryObj; - -const defaultButtonText = 'Button Text'; - -export const DefaultButton: Story = { - args: { - schema: 'primary', - variant: 'filled', - disabled: false, - children: defaultButtonText, - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const button = canvas.getByRole('button', { name: defaultButtonText }); - await userEvent.click(button); - await expect(button).toBeInTheDocument(); - }, -}; - -export const Showcase: Story = { - parameters: { - layout: 'fullscreen', - }, - render: function Render() { - const renderButtonRow = ( - schema: ButtonSchema, - variant: ButtonVariant, - disabled?: boolean, - showIcon?: boolean, - showTitle?: boolean, - ) => { - return ( -
- {buttonSizes.map((size) => ( - - {showIcon && } - {showTitle && {size} Button} - - ))} -
- ); - }; - - const renderButtonContainer = ( - schema: ButtonSchema, - variant: ButtonVariant, - disabled?: boolean, - title?: boolean, - ) => { - return ( -
- {title && ( -
-

{variant}

-
- )} - {renderButtonRow(schema, variant, disabled, false, true)} - {renderButtonRow(schema, variant, disabled, true, false)} - {renderButtonRow(schema, variant, disabled, true, true)} -
- ); - }; - - return ( -
- {buttonSchemas.map((schema) => ( -
-

{schema}

- - {buttonVariants.map((variant) => - renderButtonContainer(schema, variant, false, schema === 'primary'), - )} -
- ))} -
-

Disabled

- - {buttonVariants.map((variant) => renderButtonContainer('primary', variant, true))} -
-
- ); - }, -}; diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss b/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss deleted file mode 100644 index dc83821ca..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +++ /dev/null @@ -1,604 +0,0 @@ -@use '../../../../styles/typography'; - -$button-size-tiny: 16px; -$button-size-small: 28px; -$button-size-default: 32px; -$button-size-large: 40px; - -@mixin content-size-tiny { - @include typography.body-xs-md; - height: $button-size-tiny; - border-radius: var(--4xs); - --icon-width-tiny: 13px; -} - -@mixin content-size-small { - @include typography.body-xs-md; - height: $button-size-small; -} - -@mixin content-size-medium { - height: $button-size-default; -} - -@mixin content-size-large { - @include typography.body-md-md; - height: $button-size-large; -} - -.button { - @include typography.body-sm-md; - display: flex; - align-items: center; - justify-content: center; - white-space: nowrap; - cursor: pointer; - background: none; - outline: none; - padding-block: 0; - padding-inline: 0; - border: 2px solid transparent; - border-radius: var(--3xs); - - .content { - display: flex; - align-items: center; - justify-content: center; - gap: var(--3xs); - padding: 0 var(--sm); - border: 1px solid transparent; - border-radius: var(--3xs); - flex-grow: 1; - } - - &.tiny .content { - @include content-size-tiny; - } - - &.small .content { - @include content-size-small; - } - - &.medium .content { - @include content-size-medium; - } - - &.large .content { - @include content-size-large; - } - - &.iconButton { - aspect-ratio: 1 / 1; - align-items: stretch; - - .content { - flex: 1; - padding: 0; - } - - &.large .content { - height: $button-size-large; - width: $button-size-large; - } - - &.medium .content { - height: $button-size-default; - width: $button-size-default; - } - - &.small .content { - height: $button-size-small; - width: $button-size-small; - } - - &.tiny .content { - height: $button-size-tiny; - width: $button-size-tiny; - } - } - - &.primary-filled { - border: 2px solid transparent; - color: var(--font-on-action); - - .content { - background: var(--background-primary); - } - - &:hover { - .content { - background: var(--background-primary-hover); - } - } - - &:focus-visible { - border-color: var(--background-primary); - - .content { - border-color: var(--background); - border-radius: calc(var(--3xs) / 2); - } - } - - &:disabled { - cursor: not-allowed; - - .content { - background: var(--background-disable); - border-color: var(--background-disable); - } - } - } - - &.secondary-filled { - background: transparent; - border: 2px solid transparent; - color: var(--background-action); - - .content { - background: var(--background); - border-color: var(--border); - } - - &:hover { - &:not(:disabled) { - .content { - border-color: var(--background-action-hover); - background: var(--background-action-hover-weak); - } - } - - &:focus-visible { - background: var(--background); - - .content { - border-color: var(--background-action-secondary); - } - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-action-hover); - - .content { - border-color: var(--background); - background: var(--background-action-hover-weak); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-disable); - - .content { - border-color: var(--background-disable); - } - } - } - - &.tertiary-filled { - background: transparent; - border: 2px solid transparent; - color: var(--background-action); - - .content { - background: transparent; - border-color: transparent; - } - - &:hover:not(:disabled) { - .content { - border-color: var(--border); - background: var(--background-action-hover-weak); - } - } - - &:active { - .content { - border-color: var(--border); - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-action-hover); - - .content { - border-color: var(--background); - background: var(--background-action-hover-weak); - border-radius: calc(var(--3xs) / 2); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-disable); - } - } - - &.secondary-ghost { - background: transparent; - border: 2px solid transparent; - color: var(--background-primary); - - .content { - background: var(--background-action-secondary); - border-color: var(--background-primary); - } - - &:hover { - &:not(:disabled) { - .content { - background: var(--background-action-secondary-hover); - } - } - - &:focus-visible { - .content { - border-color: var(--background-action-secondary); - } - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-primary); - - .content { - border-color: var(--background); - background: var(--background-action-secondary-hover); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-disable); - - .content { - border-color: var(--background-disable); - } - } - } - - &.tertiary-ghost { - background: transparent; - border: 2px solid transparent; - color: var(--background-primary); - - .content { - background: transparent; - border-color: transparent; - } - - &:hover:not(:disabled) { - .content { - background: var(--background-action-secondary-hover); - } - } - - &:active { - .content { - border-color: var(--border); - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-primary); - - .content { - border-color: var(--background); - background: var(--background-action-secondary-hover); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-disable); - } - } - - &.primary-danger { - border: 2px solid transparent; - color: var(--font-on-action); - - .content { - background: var(--background-error); - } - - &:hover { - .content { - background: var(--background-error-hover); - } - } - - &:focus-visible { - border-color: var(--background-error); - - .content { - border-color: var(--background); - border-radius: calc(var(--3xs) / 2); - } - } - - &:disabled { - cursor: not-allowed; - - .content { - background: var(--background-disable); - border-color: var(--background-disable); - } - } - } - - &.secondary-danger { - background: transparent; - border: 2px solid transparent; - color: var(--background-error); - - .content { - background: var(--background); - border-color: var(--background-error); - } - - &:hover { - &:not(:disabled) { - color: var(--background-error-hover); - - .content { - border-color: var(--background-error-hover); - background: var(--background-error-secondary-hover); - } - } - - &:focus-visible { - .content { - border-color: var(--background-action-secondary); - } - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-error); - - .content { - color: var(--background-error-hover); - border-color: var(--background); - background: var(--background-error-secondary-hover); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-disable); - - .content { - border-color: var(--background-disable); - } - } - } - - &.tertiary-danger { - background: transparent; - border: 2px solid transparent; - color: var(--background-error); - - .content { - background: transparent; - border-color: transparent; - } - - &:hover:not(:disabled) { - .content { - border-color: var(--background-error-hover); - background: var(--background-error-secondary-hover); - } - - &:focus-visible { - background: var(--background); - - .content { - border-color: var(--background); - } - } - } - - &:focus-visible { - background: var(--background); - border-color: var(--background-error); - - .content { - border-color: var(--background); - background: var(--background-error-secondary-hover); - } - } - - &:disabled { - cursor: not-allowed; - background: var(--background); - color: var(--background-disable); - } - } - - &.primary-dark { - border: 2px solid transparent; - color: var(--font-on-action); - - .content { - background: var(--light-buttons-primary); - } - - &:hover:not(:disabled) { - .content { - background: var(--light-buttons-primary-hover); - } - } - - &:active:not(:disabled) { - .content { - background: var(--light-buttons-primary-hover); - } - } - - &:focus-visible { - background: var(--background-brand); - border-color: var(--light-buttons-primary); - - .content { - border-color: var(--font-on-action); - border-radius: calc(var(--3xs) / 2); - } - } - - &:disabled { - cursor: not-allowed; - - .content { - background: var(--background-action-inverse); - color: var(--background-deselected); - } - } - } - - &.secondary-dark { - background: transparent; - border: 2px solid transparent; - color: var(--light-buttons-primary-hover); - - .content { - background: transparent; - border-color: var(--light-buttons-primary-hover); - } - - &:hover:not(:disabled) { - .content { - background: var(--light-buttons-secondary-background); - border-color: var(--light-buttons-secondary-hover); - color: var(--light-buttons-secondary-hover); - } - } - - &:active:not(:disabled) { - .content { - background: transparent; - border-color: var(--light-buttons-secondary-hover); - color: var(--light-buttons-secondary-hover); - } - } - - &:focus-visible { - background: transparent; - border-color: var(--light-buttons-secondary-hover); - - .content { - border-color: transparent; - background: var(--light-buttons-secondary-background); - color: var(--light-buttons-secondary-hover); - border-radius: calc(var(--3xs) / 2); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-deselected); - - .content { - border-color: var(--background-deselected); - } - } - } - - &.secondary-light-dark { - background: transparent; - border: 2px solid transparent; - color: var(--light-buttons-secondary-light); - - .content { - background: transparent; - border-color: var(--light-buttons-secondary-light); - } - - &:hover:not(:disabled) { - .content { - background: var(--light-buttons-secondary-light-hover-background); - border-color: var(--border); - color: var(--light-buttons-tertiary); - } - } - - &:active:not(:disabled) { - .content { - background: transparent; - border-color: var(--background-secondary); - color: var(--font-on-action); - } - } - - &:focus-visible { - background: transparent; - border-color: var(--light-buttons-tertiary); - border-radius: var(--2xs); - - .content { - background: var(--Dark-Buttons-secondary-light-hover-background); - color: var(--light-buttons-tertiary); - border-radius: var(--3xs); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-deselected); - - .content { - border-color: var(--background-deselected); - } - } - } - - &.tertiary-dark { - background: transparent; - border: 2px solid transparent; - color: var(--light-buttons-tertiary); - - .content { - background: transparent; - border-color: transparent; - } - - &:hover:not(:disabled) { - .content { - background: var(--light-buttons-secondary-light-hover-background); - border-color: var(--border); - color: var(--light-buttons-tertiary); - } - } - - &:active:not(:disabled) { - .content { - background: transparent; - border-color: var(--background-secondary); - color: var(--font-on-action); - } - } - - &:focus-visible { - background: transparent; - border-color: var(--light-buttons-tertiary); - border-radius: var(--2xs); - - .content { - background: var(--Dark-Buttons-secondary-light-hover-background); - color: var(--light-buttons-tertiary); - border-radius: var(--3xs); - } - } - - &:disabled { - cursor: not-allowed; - color: var(--background-deselected); - } - } -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.module.scss b/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.module.scss deleted file mode 100644 index 408c5afd9..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -.showcaseContainer { - background: var(--color-dap-gray-100); - padding: 24px; - overflow: auto; -} - -.showcaseTable { - border-collapse: collapse; - width: 100%; -} - -.showcaseHeader { - text-align: center; - vertical-align: middle; - padding: 16px; -} - -.showcaseCell { - padding: 16px; - text-align: center; - vertical-align: middle; -} - -.showcaseCellBold { - font-weight: bold; - padding-right: 8px; -} - -.showcaseCellInline { - display: inline-block; -} - -.showcaseCellDark { - background: var(--brand-500-default); - color: var(--secondary-050); -} diff --git a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.tsx b/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.tsx deleted file mode 100644 index 7f452df82..000000000 --- a/packages/design-system/src/components/ds-button/versions/ds-button-new/ds-button-new.stories.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { fn } from 'storybook/test'; -import classNames from 'classnames'; -import { DsIcon } from '../../../ds-icon'; -import { DsSpinner } from '../../../ds-spinner'; -import DsButtonNew from './ds-button-new'; -import { buttonSizes, buttonTypes, buttonVariants } from './ds-button-new.types'; -import styles from './ds-button-new.stories.module.scss'; - -const meta: Meta = { - // eslint-disable-next-line @drivenets/ds-internal/consistent-story-titles -- We want to show user something different than code. - title: 'Components/Button', - component: DsButtonNew, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - layout: 'centered', - }, - // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - buttonType: { - control: { type: 'select' }, - options: buttonTypes, - description: 'Button type', - table: { - defaultValue: { - summary: 'primary', - }, - }, - }, - variant: { - control: { type: 'select' }, - options: buttonVariants, - table: { - defaultValue: { - summary: 'filled', - }, - }, - }, - size: { - control: { type: 'select' }, - options: buttonSizes, - table: { - defaultValue: { - summary: 'medium', - }, - }, - }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn() }, -}; -export default meta; -type Story = StoryObj; - -const defaultButtonText = 'Button Text'; - -export const DefaultButton: Story = { - args: { - buttonType: 'primary', - variant: 'filled', - size: 'large', - disabled: false, - children: ( - <> -
- + NetGen - +
@@ -185,7 +185,7 @@ const ContentHeaderActions = () => { return ( <> - + { }, }} /> - + New - + ); }; @@ -285,9 +285,9 @@ export const Empty: Story = { No matching records found. - + Clear filters - + diff --git a/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.types.ts b/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.types.ts index b2aaf4465..8caa822ff 100644 --- a/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +++ b/packages/design-system/src/components/ds-comment-bubble/ds-comment-bubble.types.ts @@ -91,7 +91,7 @@ export interface DsCommentBubbleProps { */ onMessageResolved?: (messageId: string) => void; /** - * Optional reference chip shown in the bubble header. A string is rendered as a + * Optional reference tag shown in the bubble header. A string is rendered as a * `DsTag` with a sell icon; any other `ReactNode` is rendered verbatim. */ referenceTag?: ReactNode; diff --git a/packages/design-system/src/components/ds-comment-card/ds-comment-card.types.ts b/packages/design-system/src/components/ds-comment-card/ds-comment-card.types.ts index 0217a7233..2a8c0e8f9 100644 --- a/packages/design-system/src/components/ds-comment-card/ds-comment-card.types.ts +++ b/packages/design-system/src/components/ds-comment-card/ds-comment-card.types.ts @@ -73,16 +73,16 @@ export interface CommentData { */ isResolved: boolean; /** - * Ordered list of messages in the thread, from oldest to newest. The first + * Ordered the list of messages in the thread, from oldest to newest. The first * message is typically the top-level comment body. */ messages: CommentMessage[]; /** - * Optional tag labels used for filtering and grouping in the comments drawer. + * Optional tag labels used for filtering and grouping in the comment's drawer. */ labels?: string[]; /** - * Optional reference chip shown in the card header. A string renders as a tag; + * Optional reference tag shown in the card header. A string renders as a tag; * any other `ReactNode` renders verbatim. */ referenceTag?: ReactNode; diff --git a/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts b/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts index ac0654ad6..0f3dd1157 100644 --- a/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts +++ b/packages/design-system/src/components/ds-comments-drawer/comments-filters.ts @@ -3,8 +3,13 @@ import type { FilterTag, CommentsFilterState } from './comments-filters.types'; export type { FilterTag, CommentsFilterState } from './comments-filters.types'; +/** Formats using local calendar date (not UTC) to stay consistent with applyFilters, + * which treats dateFrom/dateTo as local dates via setHours(0,0,0,0). */ const formatFilterDateForTag = (d: Date): string => { - return d.toISOString().split('T')[0] ?? ''; + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + return `${String(year)}-${month}-${day}`; }; export const initialFilterState: CommentsFilterState = { diff --git a/packages/design-system/src/components/ds-comments-drawer/comments-filters.types.ts b/packages/design-system/src/components/ds-comments-drawer/comments-filters.types.ts index a220bef47..28ded6be8 100644 --- a/packages/design-system/src/components/ds-comments-drawer/comments-filters.types.ts +++ b/packages/design-system/src/components/ds-comments-drawer/comments-filters.types.ts @@ -1,19 +1,19 @@ export interface FilterTag { /** - * Stable identifier for the chip, used when removing it from the filter state + * Stable identifier for the tag, used when removing it from the filter state */ id: string; /** - * Human-readable label rendered inside the chip + * Human-readable label rendered inside the tag */ label: string; /** - * Identifier of the filter category this chip belongs to (e.g., `authors`, + * Identifier of the filter category this tag belongs to (e.g., `authors`, * `labels`, `statuses`). Maps to a key on `CommentsFilterState`. */ filterId: string; /** - * Underlying filter value this chip represents (e.g., an author id or a status + * Underlying filter value this tag represents (e.g., an author id or a status * value). Used to remove the value from the corresponding array in * `CommentsFilterState`. */ diff --git a/packages/design-system/src/components/ds-popover/ds-popover.stories.tsx b/packages/design-system/src/components/ds-popover/ds-popover.stories.tsx index 917f29d40..301bc9b9b 100644 --- a/packages/design-system/src/components/ds-popover/ds-popover.stories.tsx +++ b/packages/design-system/src/components/ds-popover/ds-popover.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { DsButtonV3 } from '../ds-button-v3'; +import { DsButton } from '../ds-button'; import { DsIcon } from '../ds-icon'; import { DsAvatar } from '../ds-avatar'; import { DsDivider } from '../ds-divider'; @@ -51,7 +51,7 @@ export const WithContentItemsAndCTA: Story = { render: (args) => ( - Release lock + Release lock }> @@ -78,9 +78,9 @@ export const WithContentItemsAndCTA: Story = { - + Confirm - + @@ -91,7 +91,7 @@ export const SingleContentItem: Story = { render: (args) => ( - Set element status + Set element status }> @@ -117,7 +117,7 @@ export const SingleContentItem: Story = { export const Legacy: Story = { tags: ['deprecated'], render: () => ( - Legacy call form}> + Legacy call form}> }> Legacy call form @@ -134,7 +134,7 @@ export const WithImage: Story = { render: (args) => ( - London + London }> diff --git a/packages/design-system/src/components/ds-select/__tests__/ds-select.browser.test.tsx b/packages/design-system/src/components/ds-select/__tests__/ds-select.browser.test.tsx index e4321effe..bb6025063 100644 --- a/packages/design-system/src/components/ds-select/__tests__/ds-select.browser.test.tsx +++ b/packages/design-system/src/components/ds-select/__tests__/ds-select.browser.test.tsx @@ -191,7 +191,7 @@ describe('DsSelect', () => { await expect.element(trigger).toHaveTextContent(PLACEHOLDER); }); - it('should select all, expand +N chip, delete chip, and clear all (MultiSelect story)', async () => { + it('should select all, expand +N tag, delete tag, and clear all (MultiSelect story)', async () => { await page.render(); const trigger = page.getByRole('combobox'); @@ -200,12 +200,12 @@ describe('DsSelect', () => { await page.getByRole('option', { name: 'All' }).click(); - const expandChip = page.getByRole('button', { name: /^\+\d+$/ }); - await expandChip.click(); + const expandTag = page.getByRole('button', { name: /^\+\d+$/ }); + await expandTag.click(); for (const option of mockOptions) { - const chip = page.getByRole('button', { name: exactAriaName(option.label) }); - await expect.element(chip).toBeInTheDocument(); + const tag = page.getByRole('button', { name: exactAriaName(option.label) }); + await expect.element(tag).toBeInTheDocument(); } await trigger.click(); @@ -217,11 +217,11 @@ describe('DsSelect', () => { await trigger.click(); const firstOption = mockOptions[0] as DsSelectOption; - const firstOptionChip = page.getByRole('button', { name: exactAriaName(firstOption.label) }); - const deleteButton = firstOptionChip.getByRole('button', { name: 'Delete chip' }); + const firstOptionTag = page.getByRole('button', { name: exactAriaName(firstOption.label) }); + const deleteButton = firstOptionTag.getByRole('button', { name: 'Delete tag' }); await deleteButton.click(); - await expect.element(firstOptionChip).not.toBeInTheDocument(); + await expect.element(firstOptionTag).not.toBeInTheDocument(); await page.getByRole('button', { name: 'Clear All' }).click(); @@ -299,7 +299,7 @@ describe('DsSelect', () => { await expect.element(trigger).toHaveTextContent('France'); }); - it('should render selected items as chips with custom option markup (CustomRenderOptionMultiSelect story)', async () => { + it('should render selected items as tags with custom option markup (CustomRenderOptionMultiSelect story)', async () => { await page.render( name diff --git a/packages/design-system/src/components/ds-split-button/ds-split-button.types.ts b/packages/design-system/src/components/ds-split-button/ds-split-button.types.ts index ada66fbc3..722d58f60 100644 --- a/packages/design-system/src/components/ds-split-button/ds-split-button.types.ts +++ b/packages/design-system/src/components/ds-split-button/ds-split-button.types.ts @@ -12,7 +12,7 @@ type SelectSlotProps = DistributiveOmit; export interface DsSplitButtonSlotProps { /** - * Props forwarded to the primary action button (`DsButtonV3`). `variant`, `color` + * Props forwarded to the primary action button (`DsButton`). `variant`, `color` * and `size` are controlled by the split button and cannot be overridden here. */ button: ButtonSlotProps; diff --git a/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx b/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx index cc96752dd..97ec7f203 100644 --- a/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx +++ b/packages/design-system/src/components/ds-status-badge-v2/__tests__/ds-status-badge-v2.browser.test.tsx @@ -39,7 +39,7 @@ describe('DsStatusBadgeV2', () => { const badge = page.getByRole('status').element(); const icon = badge.querySelector('[aria-hidden="true"]'); expect(icon).toBeTruthy(); - expect(icon!.textContent).toBe('star'); + expect(icon?.textContent).toBe('star'); }); it('iconOnly hides label and shows tooltip with label text on hover', async () => { diff --git a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss index 50937011d..61e8c736d 100644 --- a/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +++ b/packages/design-system/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss @@ -7,7 +7,7 @@ $border-radius: 8px; bottom: 20px; left: 50%; transform: translateX(-50%); - background-color: var(--white); + background-color: var(--color-dap-gray-050); border-radius: $border-radius; box-shadow: 0 4px 12px #0445cc66; z-index: 1; @@ -40,12 +40,12 @@ $border-radius: 8px; } .selectedCountBadge { - color: var(--white); + color: var(--color-dap-gray-050); margin: 0; } .bulkActionsInfo { - color: var(--neutral-2); + color: var(--color-dap-gray-600); text-wrap: nowrap; padding-left: 8px; padding-right: 56px; @@ -56,7 +56,7 @@ $border-radius: 8px; flex-direction: column; align-items: center; justify-content: center; - gap: var(--spacing-3xs); + gap: var(--3xs); width: 68px; height: 48px; border-radius: 0; @@ -76,7 +76,7 @@ $border-radius: 8px; right: 0; width: 1px; height: 100%; - background-color: var(--neutral-6); + background-color: var(--color-dap-gray-400); } &:first-child::before { @@ -86,7 +86,7 @@ $border-radius: 8px; left: 0; width: 1px; height: 100%; - background-color: var(--neutral-6); + background-color: var(--color-dap-gray-400); } } } @@ -96,14 +96,14 @@ $border-radius: 8px; flex-direction: column; justify-content: center; align-items: center; - color: var(--neutral-2); + color: var(--color-dap-gray-600); padding-left: 4px; margin-right: 2px; background: none; border-radius: 0 $border-radius $border-radius 0; &:hover { - color: var(--neutral-4); + color: var(--color-dap-gray-500); background: inherit; } } diff --git a/packages/design-system/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx b/packages/design-system/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx index 96a366372..12c0cdd61 100644 --- a/packages/design-system/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +++ b/packages/design-system/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx @@ -1,5 +1,5 @@ import { flexRender } from '@tanstack/react-table'; -import { DsButtonV3 } from '../../../ds-button-v3'; +import { DsButton } from '../../../ds-button'; import { TableHead } from '../core-table'; import { useDsTableContext } from '../../context/ds-table-context'; import { getGroupColumnSizeStyle } from '../../grouping'; @@ -31,7 +31,7 @@ export const DsTableGroupHeaderCell = ({ {flexRender(header.column.columnDef.header, header.getContext())} {isCollapsible && ( - ({ aria-label="Double-click to edit" > {children} - ({ cell }: DsTableEditingCellPr {columnDef.editCell ? flexRender(columnDef.editCell, cell.getContext()) : null} - ({ cell }: DsTableEditingCellPr }} style={{ color: 'var(--icon-error)' }} /> - ` @@ -53,7 +53,7 @@ export const DsTableColumnFilterPopover = ({ positioning={{ placement: 'bottom-end', gutter: 4 }} > - event.stopPropagation()} > - + @@ -74,12 +74,12 @@ export const DsTableColumnFilterPopover = ({
{children}
- + {clearLabel} - - + + {saveLabel} - +
diff --git a/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx b/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx index 7040f657c..9ff98134b 100644 --- a/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx +++ b/packages/design-system/src/components/ds-table/stories/ds-table-column-filters.stories.tsx @@ -310,11 +310,7 @@ draft and closes it (same as pressing Escape). return (
{filterTags.length > 0 && ( - + )} {filterTags.length > 0 && ( - + )}
- + Close - +
@@ -94,13 +94,13 @@ export const Default: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -127,12 +127,12 @@ export const Default: Story = {
v1.2.0
- + Help - - + + Feedback - +
@@ -149,9 +149,9 @@ export const WithDrawer: Story = {
- + Close - +
@@ -165,13 +165,13 @@ export const WithDrawer: Story = { Last update: 2d ago
- + Discard - - setDrawerOpen(true)}> + + setDrawerOpen(true)}> Save project - - + +
@@ -203,12 +203,12 @@ export const WithDrawer: Story = { - setDrawerOpen(false)}> + setDrawerOpen(false)}> Cancel - - + + Save - + @@ -233,9 +233,9 @@ export const WithDrawerAndBackdrop: Story = {
- + Close - +
@@ -249,13 +249,13 @@ export const WithDrawerAndBackdrop: Story = { Last update: 2d ago
- + Discard - - setDrawerOpen(true)}> + + setDrawerOpen(true)}> Save project - - + +
@@ -303,9 +303,9 @@ export const FillParent: Story = {
- + Close - +
@@ -319,13 +319,13 @@ export const FillParent: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -352,9 +352,9 @@ export const HeaderOnly: Story = {
- + Close - +
@@ -368,13 +368,13 @@ export const HeaderOnly: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -398,9 +398,9 @@ export const ExtendedStepperBelow: Story = {
- + Close - +
@@ -414,13 +414,13 @@ export const ExtendedStepperBelow: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -429,9 +429,9 @@ export const ExtendedStepperBelow: Story = {
Project workspace - + Edit - +
@@ -468,9 +468,9 @@ export const ExtendedStepperAside: Story = {
- + Close - +
@@ -484,13 +484,13 @@ export const ExtendedStepperAside: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -524,9 +524,9 @@ export const ExtendedStepperAside: Story = {
Project workspace - + Edit - +
@@ -551,9 +551,9 @@ export const ExtendedSideMenuAndLeftPanel: Story = {
- + Close - +
@@ -567,13 +567,13 @@ export const ExtendedSideMenuAndLeftPanel: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -626,9 +626,9 @@ export const ExtendedSideMenuAndLeftPanel: Story = {
Project workspace - + Edit - +
@@ -651,9 +651,9 @@ export const ExtendedWithCanvas: Story = {
- + Close - +
@@ -667,13 +667,13 @@ export const ExtendedWithCanvas: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -681,9 +681,9 @@ export const ExtendedWithCanvas: Story = {
Network topology - + Expand - +
@@ -706,9 +706,9 @@ export const ExtendedSideMenuLeftPanel: Story = {
- + Close - +
@@ -722,13 +722,13 @@ export const ExtendedSideMenuLeftPanel: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
@@ -794,9 +794,9 @@ export const ExtendedSideMenuLeftPanel: Story = {
Project workspace - + Edit - +
@@ -823,9 +823,9 @@ export const ExtendedCombined: Story = {
- + Close - +
@@ -839,13 +839,13 @@ export const ExtendedCombined: Story = { Last update: 2d ago
- + Discard - - setDrawerOpen(true)}> + + setDrawerOpen(true)}> Save project - - + +
@@ -911,9 +911,9 @@ export const ExtendedCombined: Story = {
Project workspace - + Edit - +
@@ -950,9 +950,9 @@ export const ExtendedWorkflowInfoPanel: Story = {
- + Close - +
@@ -966,13 +966,13 @@ export const ExtendedWorkflowInfoPanel: Story = { Last update: 2d ago
- + Discard - - + + Save project - - + +
diff --git a/packages/design-system/src/stories/patterns-forms.mdx b/packages/design-system/src/stories/patterns-forms.mdx index 77754397d..41e4cba53 100644 --- a/packages/design-system/src/stories/patterns-forms.mdx +++ b/packages/design-system/src/stories/patterns-forms.mdx @@ -26,7 +26,7 @@ reference see the [`Examples/SampleForm`](?path=/story/examples-sampleform--defa [`DsTimePicker`](?path=/docs/design-system-timepicker--docs) - [`DsRadioGroup`](?path=/docs/design-system-radiogroup--docs) / [`DsCheckbox`](?path=/docs/design-system-checkbox--docs) -- [`DsButtonV3`](?path=/docs/design-system-buttonv3--docs) +- [`DsButtonV`](?path=/docs/design-system-button--docs) - [`DsTypography`](?path=/docs/design-system-typography--docs) ## Stack @@ -258,7 +258,7 @@ Avoid disabling submit purely based on validation state. Users should be able to - Disable submit when no meaningful action can occur (e.g. no changes in edit forms) - Always disable while submitting, and surface the in-flight state through - [`DsButtonV3`](?path=/docs/design-system-buttonv3--docs)'s `loading` prop so the button renders its + [`DsButton`](?path=/docs/design-system-button--docs)'s `loading` prop so the button renders its spinner in place of the label - Do not gate submit on isValid @@ -267,9 +267,9 @@ For edit/update forms, disable until the user has made changes: + code={` Submit -`} +`} /> For create forms, keep submit enabled and rely on validation on submit: @@ -277,9 +277,9 @@ For create forms, keep submit enabled and rely on validation on submit: + code={` Submit -`} +`} /> ## Checklist diff --git a/packages/design-system/src/stories/sample-form/sample-form.tsx b/packages/design-system/src/stories/sample-form/sample-form.tsx index 0b7968c91..bb6a891a6 100644 --- a/packages/design-system/src/stories/sample-form/sample-form.tsx +++ b/packages/design-system/src/stories/sample-form/sample-form.tsx @@ -7,7 +7,7 @@ import { DsCheckbox } from '../../components/ds-checkbox'; import { DsTypography } from '../../components/ds-typography'; import { sampleFormSchema, type SampleFormValues } from './sample-form-schema'; import { useState } from 'react'; -import { DsButtonV3 } from '../../components/ds-button-v3'; +import { DsButton } from '../../components/ds-button'; const defaultValues = { name: '', @@ -235,9 +235,9 @@ const SampleForm = () => { )} /> - + Submit - + );