Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components-html/src/menu-link/menu-link.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
============================================================================= */

.dsn-menu-link__link[aria-current='page'] {
font-weight: var(--dsn-menu-link-current-font-weight);
color: var(--dsn-menu-link-current-color);
background-color: var(--dsn-menu-link-current-background-color);
border-inline-start: var(--dsn-menu-link-current-indicator-width) solid
Expand Down
5 changes: 5 additions & 0 deletions packages/design-tokens/src/tokens/components/menu-link.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
}
},
"current": {
"font-weight": {
"value": "{dsn.text.font-weight.bold}",
"type": "fontWeight",
"comment": "Font weight voor de actieve/huidige pagina — bold voor visuele nadruk"
},
"color": {
"value": "{dsn.color.action-2.color-default}",
"type": "color",
Expand Down
248 changes: 190 additions & 58 deletions packages/storybook/src/MenuLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,92 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Icon, MenuLink, NumberBadge } from '@dsn/components-react';
import type { IconName } from '@dsn/components-react/icon-registry.generated';
import DocsPage from './MenuLink.docs.mdx';
import {
TEKST,
VEEL_TEKST,
TEKST_AR,
VEEL_TEKST_AR,
rtlDecorator,
} from './story-helpers';

// =============================================================================
// ICON OPTIONS (zelfde lijst als Link/Button stories)
// =============================================================================

const iconOptions: (IconName | undefined)[] = [
undefined,
'alert-triangle',
'archive',
'arrow-down',
'arrow-left',
'arrow-narrow-down',
'arrow-narrow-up',
'arrow-right',
'arrow-up',
'bell',
'calendar-event',
'check',
'chevron-down',
'chevron-left',
'chevron-right',
'chevron-up',
'circle-check',
'clock',
'dots-vertical',
'download',
'edit',
'exclamation-circle',
'external-link',
'eye',
'file-description',
'folder',
'heart-filled',
'heart',
'home',
'info-circle',
'loader',
'mail',
'menu',
'message-circle',
'minus',
'paperclip',
'plus',
'search',
'selector',
'settings',
'star-filled',
'star',
'trash',
'upload',
'user',
'x',
];

const iconMapping = iconOptions.reduce(
(acc, icon) => {
acc[icon ?? 'undefined'] = icon ? (
<Icon name={icon} aria-hidden />
) : undefined;
return acc;
},
{} as Record<string, React.ReactNode>
);

// =============================================================================
// NUMBER BADGE OPTIONS
// =============================================================================

const badgeOptions: Record<string, React.ReactNode> = {
'(geen)': undefined,
'5 (negative)': <NumberBadge variant="negative">5</NumberBadge>,
'12 (negative)': <NumberBadge variant="negative">12</NumberBadge>,
'99 (neutral)': <NumberBadge variant="neutral">99</NumberBadge>,
};

// =============================================================================
// META
// =============================================================================

const meta: Meta<typeof MenuLink> = {
title: 'Components/MenuLink',
Expand Down Expand Up @@ -28,10 +114,25 @@ const meta: Meta<typeof MenuLink> = {
subItems: { control: 'boolean' },
expanded: { control: 'boolean' },
children: { control: 'text' },
iconStart: {
control: 'select',
options: iconOptions,
mapping: iconMapping,
},
iconEnd: {
control: 'select',
options: iconOptions,
mapping: iconMapping,
},
numberBadge: {
control: 'select',
options: Object.keys(badgeOptions),
mapping: badgeOptions,
},
},
args: {
href: '/dashboard',
children: 'Dashboard',
children: TEKST,
level: 1,
current: false,
subItems: false,
Expand All @@ -42,47 +143,45 @@ const meta: Meta<typeof MenuLink> = {
export default meta;
type Story = StoryObj<typeof MenuLink>;

// Helper: wikkelt een enkel MenuLink in een semantisch correcte <ul>
const renderSingle = (args: React.ComponentProps<typeof MenuLink>) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
);

// =============================================================================
// DEFAULT
// =============================================================================

export const Default: Story = {
render: (args) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
),
render: renderSingle,
};

// =============================================================================
// CURRENT (ACTIEVE PAGINA)
// VARIANTEN
// =============================================================================

export const Current: Story = {
name: 'Current (actieve pagina)',
args: { current: true, children: 'Rapporten' },
render: (args) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
),
args: { current: true },
render: renderSingle,
};

// =============================================================================
// MET ICOON
// =============================================================================

export const WithIconStart: Story = {
name: 'Met icoon (iconStart)',
name: 'Met icoon start',
args: {
iconStart: <Icon name="home" aria-hidden />,
children: 'Dashboard',
},
render: (args) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
),
render: renderSingle,
};

export const WithIconEnd: Story = {
name: 'Met icoon end',
args: {
iconEnd: <Icon name="arrow-right" aria-hidden />,
},
render: renderSingle,
};

export const WithNumberBadge: Story = {
Expand All @@ -93,15 +192,32 @@ export const WithNumberBadge: Story = {
numberBadge: <NumberBadge variant="negative">5</NumberBadge>,
children: 'Inbox',
},
render: (args) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
),
render: renderSingle,
};

export const WithExpandButton: Story = {
name: 'Met uitklapknop (subItems)',
args: {
href: '/rapporten',
children: 'Rapporten',
subItems: true,
expanded: false,
},
render: renderSingle,
};

// =============================================================================
// NIVEAU-HIËRARCHIE
// TEKST VARIANTEN
// =============================================================================

export const LongText: Story = {
name: 'Long text',
args: { children: VEEL_TEKST },
render: renderSingle,
};

// =============================================================================
// OVERZICHTSSTORIES
// =============================================================================

export const Levels: Story = {
Expand All @@ -124,25 +240,6 @@ export const Levels: Story = {
),
};

// =============================================================================
// UITKLAPBAAR
// =============================================================================

export const WithExpandButton: Story = {
name: 'Met uitklapknop (subItems)',
args: {
href: '/rapporten',
children: 'Rapporten',
subItems: true,
expanded: false,
},
render: (args) => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink {...args} />
</ul>
),
};

export const ExpandedWithSubItems: Story = {
name: "Uitgevouwen met subpagina's",
render: () => (
Expand All @@ -168,10 +265,6 @@ export const ExpandedWithSubItems: Story = {
),
};

// =============================================================================
// VOLLEDIG NAVIGATIEMENU
// =============================================================================

export const FullNavigation: Story = {
name: 'Volledig navigatiemenu',
render: () => (
Expand Down Expand Up @@ -221,10 +314,6 @@ export const FullNavigation: Story = {
),
};

// =============================================================================
// ALLE VARIANTEN
// =============================================================================

export const AllStates: Story = {
name: 'Alle staten',
render: () => (
Expand All @@ -234,7 +323,13 @@ export const AllStates: Story = {
Actief (current)
</MenuLink>
<MenuLink href="/icoon" iconStart={<Icon name="home" aria-hidden />}>
Met icoon
Met icoon start
</MenuLink>
<MenuLink
href="/icoon-end"
iconEnd={<Icon name="arrow-right" aria-hidden />}
>
Met icoon end
</MenuLink>
<MenuLink
href="/badge"
Expand All @@ -258,3 +353,40 @@ export const AllStates: Story = {
</ul>
),
};

// =============================================================================
// RTL
// =============================================================================

export const RTL: Story = {
name: 'RTL',
decorators: [rtlDecorator],
render: () => (
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
<MenuLink href="/pagina">{TEKST_AR}</MenuLink>
<MenuLink href="/pagina" iconStart={<Icon name="home" aria-hidden />}>
{TEKST_AR}
</MenuLink>
<MenuLink href="/pagina" iconEnd={<Icon name="arrow-left" aria-hidden />}>
{TEKST_AR}
</MenuLink>
<MenuLink href="/pagina" current>
{TEKST_AR}
</MenuLink>
<MenuLink
href="/pagina"
iconStart={<Icon name="mail" aria-hidden />}
numberBadge={<NumberBadge variant="negative">5</NumberBadge>}
>
{TEKST_AR}
</MenuLink>
</ul>
),
};

export const RTLLongText: Story = {
name: 'RTL long text',
decorators: [rtlDecorator],
args: { children: VEEL_TEKST_AR },
render: renderSingle,
};
Loading