From 549ad64220a44cbe8a88e7fa444f72c768078931 Mon Sep 17 00:00:00 2001 From: Adham Abo Hasson Date: Mon, 14 Oct 2024 15:46:02 +0200 Subject: [PATCH] Feature/ Navigation List (#740) #557 --- packages/components-css/index.scss | 1 + .../navigation-list/_mixin.scss | 88 +++++++++++++ .../components-css/navigation-list/index.scss | 43 +++++++ .../src/NavigationList.test.tsx | 29 +++++ .../components-react/src/NavigationList.tsx | 22 ++++ .../src/NavigationListItem.test.tsx | 36 ++++++ .../src/NavigationListItem.tsx | 39 ++++++ packages/components-react/src/SideNavItem.tsx | 2 +- packages/components-react/src/icon/Icon.tsx | 4 + .../components-react/src/icon/IconTypes.ts | 2 + packages/components-react/src/index.ts | 2 + .../src/community/navigation-list-item.md | 13 ++ .../navigation-list-item.stories.tsx | 87 +++++++++++++ .../src/community/navigation-list.md | 21 +++ .../src/community/navigation-list.stories.tsx | 72 +++++++++++ .../design-tokens/figma/figma.tokens.json | 121 ++++++++++++++++++ 16 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 packages/components-css/navigation-list/_mixin.scss create mode 100644 packages/components-css/navigation-list/index.scss create mode 100644 packages/components-react/src/NavigationList.test.tsx create mode 100644 packages/components-react/src/NavigationList.tsx create mode 100644 packages/components-react/src/NavigationListItem.test.tsx create mode 100644 packages/components-react/src/NavigationListItem.tsx create mode 100644 packages/storybook/src/community/navigation-list-item.md create mode 100644 packages/storybook/src/community/navigation-list-item.stories.tsx create mode 100644 packages/storybook/src/community/navigation-list.md create mode 100644 packages/storybook/src/community/navigation-list.stories.tsx diff --git a/packages/components-css/index.scss b/packages/components-css/index.scss index 699557a9..04210ff9 100644 --- a/packages/components-css/index.scss +++ b/packages/components-css/index.scss @@ -25,6 +25,7 @@ @import "link/index"; @import "logo/index"; @import "navbar/index"; +@import "navigation-list/index"; @import "ordered-list/index"; @import "radio/index"; @import "radio-group/index"; diff --git a/packages/components-css/navigation-list/_mixin.scss b/packages/components-css/navigation-list/_mixin.scss new file mode 100644 index 00000000..7f9c1660 --- /dev/null +++ b/packages/components-css/navigation-list/_mixin.scss @@ -0,0 +1,88 @@ +/** + * @license EUPL-1.2 + * Copyright (c) 2021 Community for NL Design System + */ +@mixin rhc-navigation-list { + display: flex; + flex-direction: column; + list-style: none; + margin-block: 0; + padding-inline: 0; + &:last-child { + border-block-start-color: var(--rhc-navigation-list-item-border-color, var(--rhc-color-grijs-300)); + border-block-start-style: solid; + border-block-start-width: var(--rhc-navigation-list-item-border-width, var(--rhc-border-width-default)); + } +} +@mixin rhc-navigation-list__item { + align-items: center; + background-color: var(--rhc-navigation-list-item-background-color, var(--rhc-color-canvas)); + border-block-end-color: var(--rhc-navigation-list-item-border-color, var(--rhc-color-grijs-300)); + border-block-end-style: solid; + border-block-end-width: var(--rhc-navigation-list-item-border-width, var(--rhc-border-width-default)); + color: var(--rhc-navigation-list-item-color, var(--rhc-color-foreground-subdued)); + column-gap: var(--rhc-navigation-list-item-column-gap, var(--rhc-space-100)); + display: flex; + min-block-size: var(--rhc-navigation-list-item-min-height, var(--rhc-size-target)); + padding-block: var(--rhc-navigation-list-item-padding-block, var(--rhc-space-100)); + padding-inline: var(--rhc-navigation-list-item-padding-inline, var(--rhc-space-200)); + position: relative; + text-decoration: none; + &:hover { + background-color: var(--rhc-navigation-list-item-hover-background-color, var(--rhc-color-grijs-50)); + } + + &:focus { + background-color: var(--rhc-navigation-list-item-focus-background-color, var(--rhc-color-lintblauw-50)); + z-index: 999; + } + + &:active { + background-color: var(--rhc-navigation-list-item-active-background-color, var(--rhc-color-grijs-100)); + } +} + +@mixin rhc-navigation-list__item__start-icon { + background-color: var(--rhc-navigation-list-icon-background-color, var(--rhc-color-wit)); + block-size: var(--rhc-navigation-list-item-icon-size); + border-radius: var(--rhc-navigation-list-icon-border-radius, var(--rhc-border-radius-circle)); + color: var(--rhc-navigation-list-icon-color, var(--rhc-color-foreground-subdued)); + grid-area: start-icon; + min-inline-size: var(--rhc-navigation-list-item-icon-size); + padding-block: var(--rhc-navigation-list-item-icon-padding-block); + padding-inline: var(--rhc-navigation-list-item-icon-padding-inline); +} + +@mixin rhc-navigation-list__item-content { + align-items: center; + display: grid; + flex: 1; + grid-column-gap: var(--rhc-navigation-list-item-content-column-gap, var(--rhc-space-100)); + grid-row-gap: var(--rhc-navigation-list-item-content-row-gap, var(--rhc-space-100)); + grid-template-areas: + "label content content end-icon" + "label content content end-icon"; + grid-template-columns: 1fr 1fr 1fr auto; +} + +@mixin rhc-navigation-list__item__label { + color: var(--rhc-navigation-list-item-heading-color, var(--rhc-color-foreground-lint)); + font-family: var(--rhc-navigation-list-item-label-font-family, var(--rhc-font-family-primary)), sans-serif; + font-size: var(--rhc-navigation-list-item-label-font-size, var(--rhc-font-size-md-desktop)); + font-weight: var(--rhc-navigation-list-item-label-font-weight, var(--rhc-font-weight-bold)); + grid-area: label; + line-height: var(--rhc-navigation-list-item-label-line-height, var(--rhc-line-height-md)); +} +@mixin rhc-navigation-list__item__description { + color: inherit; + grid-area: content; + @media (width<= 768px) { + text-align: start; + } +} + +@mixin rhc-navigation-list__item__end-icon { + align-self: center; + grid-area: end-icon; + justify-self: end; +} diff --git a/packages/components-css/navigation-list/index.scss b/packages/components-css/navigation-list/index.scss new file mode 100644 index 00000000..70c92f8f --- /dev/null +++ b/packages/components-css/navigation-list/index.scss @@ -0,0 +1,43 @@ +/** + * @license EUPL-1.2 + * Copyright (c) 2021 Community for NL Design System + */ +@import "./mixin"; + +.rhc-navigation-list { + @include rhc-navigation-list; + &__item { + @include rhc-navigation-list__item; + &-content { + @include rhc-navigation-list__item-content; + } + &__label { + @include rhc-navigation-list__item__label; + } + &__description { + @include rhc-navigation-list__item__description; + } + &__start-icon { + @include rhc-navigation-list__item__start-icon; + } + &__end-icon { + @include rhc-navigation-list__item__end-icon; + } + } +} +.rhc-navigation-list--container-small { + container-type: inline-size; +} +@container (width <= 768px) { + .rhc-navigation-list__item-content { + grid-template-areas: + "label label label end-icon" + "content content content end-icon"; + } + .rhc-navigation-list__item__start-icon { + align-self: start; + } + .rhc-navigation-list__item__description { + text-align: start; + } +} diff --git a/packages/components-react/src/NavigationList.test.tsx b/packages/components-react/src/NavigationList.test.tsx new file mode 100644 index 00000000..79c8e4b8 --- /dev/null +++ b/packages/components-react/src/NavigationList.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { NavigationList } from './NavigationList'; + +describe('NavigationList', () => { + it('renders children correctly', () => { + const testContent = 'Test NavigationList Content'; + render( + +
{testContent}
+
, + ); + + expect(screen.getByText(testContent)).toBeInTheDocument(); + }); + + it('forwards ref correctly', () => { + render(); + + expect(screen.getByRole('list')).toBeInstanceOf(HTMLUListElement); + }); + + it('applies custom class name', () => { + const testClassName = 'test-class'; + render(); + + expect(screen.getByRole('list')).toHaveClass(testClassName); + }); +}); diff --git a/packages/components-react/src/NavigationList.tsx b/packages/components-react/src/NavigationList.tsx new file mode 100644 index 00000000..5d23c4bb --- /dev/null +++ b/packages/components-react/src/NavigationList.tsx @@ -0,0 +1,22 @@ +import clsx from 'clsx'; +import { forwardRef, HTMLAttributes } from 'react'; + +export type NavigationListProps = HTMLAttributes; + +export const NavigationList = forwardRef( + ({ children, className, ...restProps }, ref) => { + return ( +
    + {children} +
+ ); + }, +); + +NavigationList.displayName = 'NavigationList'; diff --git a/packages/components-react/src/NavigationListItem.test.tsx b/packages/components-react/src/NavigationListItem.test.tsx new file mode 100644 index 00000000..0a7e8eb7 --- /dev/null +++ b/packages/components-react/src/NavigationListItem.test.tsx @@ -0,0 +1,36 @@ +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { NavigationListItem } from './NavigationListItem'; + +const label = 'label'; +const description = 'description'; + +describe('NavigationListItem', () => { + it('renders a NavigationListItem', () => { + render(); + expect(screen.getByText(description)).toBeInTheDocument(); + expect(screen.getByText(label)).toBeInTheDocument(); + expect(screen.getByRole('listitem')).toBeInTheDocument(); + }); + + it('forwards ref correctly', () => { + render(); + + expect(screen.getByRole('listitem')).toBeInstanceOf(HTMLLIElement); + }); + + it('applies custom class name', () => { + const testClassName = 'test-class'; + render( + , + ); + + expect(screen.getByRole('link')).toHaveClass(testClassName); + }); +}); diff --git a/packages/components-react/src/NavigationListItem.tsx b/packages/components-react/src/NavigationListItem.tsx new file mode 100644 index 00000000..8edfe56e --- /dev/null +++ b/packages/components-react/src/NavigationListItem.tsx @@ -0,0 +1,39 @@ +import clsx from 'clsx'; +import { forwardRef, HTMLAttributes, ReactNode } from 'react'; +import { Heading } from './Heading'; +import { Paragraph } from './Paragraph'; +import { Icon } from './icon/Icon'; + +export interface NavigationListItemProps extends HTMLAttributes { + label: ReactNode; + description: ReactNode; + icon: ReactNode; + href: string; +} + +export const NavigationListItem = forwardRef( + ({ label, href, description, icon, className, ...restProps }, ref) => { + return ( +
  • + + {typeof icon === 'string' ? ( + + ) : ( + {icon} + )} + + + {label} + + + {description} + + + + +
  • + ); + }, +); + +NavigationListItem.displayName = 'NavigationListItem'; diff --git a/packages/components-react/src/SideNavItem.tsx b/packages/components-react/src/SideNavItem.tsx index 0d07a925..2b7613bc 100644 --- a/packages/components-react/src/SideNavItem.tsx +++ b/packages/components-react/src/SideNavItem.tsx @@ -5,7 +5,7 @@ export interface SideNavItemProps extends HTMLAttributes {} export const SideNavItem = forwardRef((props, ref) => { return ( -
  • +
  • {props.children}
  • ); diff --git a/packages/components-react/src/icon/Icon.tsx b/packages/components-react/src/icon/Icon.tsx index a3330bcd..acd9db9e 100644 --- a/packages/components-react/src/icon/Icon.tsx +++ b/packages/components-react/src/icon/Icon.tsx @@ -12,6 +12,7 @@ import { IconArrowUpRight, IconBackhoe, IconBell, + IconBriefcase, IconBubble, IconCalendarCheck, IconCaretDown, @@ -24,6 +25,7 @@ import { IconClock, IconCloudLock, IconCornerLeftUp, + IconCurrencyEuro, IconDeviceFloppy, IconDeviceMobile, IconDotsVertical, @@ -87,6 +89,7 @@ export const IconenSet: Partial> = { bel: , bewerken: , blog: , + briefcase: , 'circle-check': , 'chevron-right': , comment: , @@ -98,6 +101,7 @@ export const IconenSet: Partial> = { 'delta-omlaag': , 'diagonale-pijl': , downloaden: , + 'currency-euro': , 'externe-link': , favoriet: , 'foto-vergroten': , diff --git a/packages/components-react/src/icon/IconTypes.ts b/packages/components-react/src/icon/IconTypes.ts index 52108021..5f3017f5 100644 --- a/packages/components-react/src/icon/IconTypes.ts +++ b/packages/components-react/src/icon/IconTypes.ts @@ -3,12 +3,14 @@ export type RijkshuisstijlIconID = | 'alert-circle' | 'alert-triangle' | 'arrows-sort' + | 'briefcase' | 'crisisoverleg' | 'circle-check' | 'chevron-right' | 'dansen' | 'docent-voor-klas' | 'elleboognies' + | 'currency-euro' | 'fysiotherapeut' | 'gevangene' | 'hulpverleners' diff --git a/packages/components-react/src/index.ts b/packages/components-react/src/index.ts index c0789254..32fcfca7 100644 --- a/packages/components-react/src/index.ts +++ b/packages/components-react/src/index.ts @@ -68,6 +68,8 @@ export { LinkList, LinkListLink, type LinkListLinkProps, type LinkListProps } fr export { LinkListCard } from './LinkListCard'; export { Logo, type LogoProps } from './Logo'; export { NavBar, type NavBarItemProps, type NavBarProps } from './NavBar'; +export { NavigationList, type NavigationListProps } from './NavigationList'; +export { NavigationListItem, type NavigationListItemProps } from './NavigationListItem'; export { OrderedList, OrderedListItem, type OrderedListItemProps, type OrderedListProps } from './OrderedList'; export { PageContent, type PageContentProps } from './PageContent'; export { PageHeader, type PageHeaderProps } from './PageHeader'; diff --git a/packages/storybook/src/community/navigation-list-item.md b/packages/storybook/src/community/navigation-list-item.md new file mode 100644 index 00000000..38401a87 --- /dev/null +++ b/packages/storybook/src/community/navigation-list-item.md @@ -0,0 +1,13 @@ + + +# Rijkshuisstijl Community Navigation List Item component + +NL design system - geen | [Figma](https://www.figma.com/design/Nv5EsCW9ioWBUSi9m9JqOa/Local---Rijkshuisstijl---Bibliotheek?node-id=4074-1580&node-type=canvas&t=HuDzyBW9wHdB2QVh-0) | [GitHub](https://github.com/nl-design-system/rijkshuisstijl-community/issues/557) + +## Usage + +```tsx +import { NavigationListItem } from '@rijkshuisstijl-community/components-react'; + +; +``` diff --git a/packages/storybook/src/community/navigation-list-item.stories.tsx b/packages/storybook/src/community/navigation-list-item.stories.tsx new file mode 100644 index 00000000..bac55e00 --- /dev/null +++ b/packages/storybook/src/community/navigation-list-item.stories.tsx @@ -0,0 +1,87 @@ +import { + iconOptions, + NavigationList, + NavigationListItem, + NavigationListItemProps, +} from '@rijkshuisstijl-community/components-react'; +import { Meta, StoryObj } from '@storybook/react'; +import readme from './navigation-list-item.md?raw'; + +const meta = { + title: 'Rijkshuisstijl/NavigationList/NavigationListItem', + component: NavigationListItem, + parameters: { + docs: { + description: { + component: readme, + }, + }, + }, + argTypes: { + description: { + control: { + type: 'text', + }, + }, + href: { + control: { + type: 'text', + }, + }, + icon: { + control: { + type: 'select', + }, + options: [...iconOptions], + }, + label: { + control: { + type: 'text', + }, + }, + }, + args: { + description: 'Rijkshuisstijl Community Navigation List Item', + label: 'Label', + href: '#', + icon: 'activiteit', + }, + render: (args: NavigationListItemProps) => ( + + + + ), +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithCustomIcon: Story = { + args: { + icon: ( + + + + ), + }, +}; + +export const Hover: Story = { + parameters: { + pseudo: { hover: true }, + }, +}; + +export const Focus: Story = { + parameters: { + pseudo: { focus: true }, + }, +}; diff --git a/packages/storybook/src/community/navigation-list.md b/packages/storybook/src/community/navigation-list.md new file mode 100644 index 00000000..3de793b1 --- /dev/null +++ b/packages/storybook/src/community/navigation-list.md @@ -0,0 +1,21 @@ + + +# Rijkshuisstijl Community Navigation List component + +NL design system - geen | [Figma](https://www.figma.com/design/Nv5EsCW9ioWBUSi9m9JqOa/Local---Rijkshuisstijl---Bibliotheek?node-id=4074-1580&node-type=canvas&t=HuDzyBW9wHdB2QVh-0) | [GitHub](https://github.com/nl-design-system/rijkshuisstijl-community/issues/557) + +## Usage + +```tsx +import { NavigationList, NavigationListItem, NavigationListLink } from '@rijkshuisstijl-community/components-react'; + + + + +; +``` diff --git a/packages/storybook/src/community/navigation-list.stories.tsx b/packages/storybook/src/community/navigation-list.stories.tsx new file mode 100644 index 00000000..e306a41a --- /dev/null +++ b/packages/storybook/src/community/navigation-list.stories.tsx @@ -0,0 +1,72 @@ +import { NavigationList, NavigationListItem } from '@rijkshuisstijl-community/components-react'; +import { NavigationListProps } from '@rijkshuisstijl-community/components-react/dist/NavigationList'; +import { Meta, StoryObj } from '@storybook/react'; +import readme from './navigation-list.md?raw'; + +const meta = { + title: 'Rijkshuisstijl/NavigationList', + component: NavigationList, + parameters: { + docs: { + description: { + component: readme, + }, + }, + }, + argTypes: {}, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args: NavigationListProps) => ( + + + + + + ), +}; +export const SmallContainer: Story = { + render: (args: NavigationListProps) => ( +
    + + + + + +
    + ), +}; diff --git a/proprietary/design-tokens/figma/figma.tokens.json b/proprietary/design-tokens/figma/figma.tokens.json index 4d718c4e..13166dec 100644 --- a/proprietary/design-tokens/figma/figma.tokens.json +++ b/proprietary/design-tokens/figma/figma.tokens.json @@ -4085,6 +4085,126 @@ } } }, + "components/navigation-list": { + "rhc": { + "navigation-list": { + "icon": { + "background-color": { + "value": "{rhc.color.foreground.lint}", + "type": "color" + }, + "border-radius": { + "value": "{rhc.border-radius.circle}", + "type": "borderRadius" + }, + "color": { + "value": "{rhc.color.foreground.onEmphasis}", + "type": "color" + } + }, + "item": { + "icon": { + "padding-inline": { + "value": "{rhc.space.100}", + "type": "space" + }, + "padding-block": { + "value": "{rhc.space.100}", + "type": "space" + }, + "size": { + "value": "{rhc.size.icon.functional}", + "type": "size" + } + }, + "content": { + "column-gap": { + "value": "{rhc.space.100}", + "type": "space" + }, + "row-gap": { + "value": "{rhc.space.50}", + "type": "space" + } + }, + "label": { + "font-family": { + "value": "{rhc.font-family.primary}", + "type": "font-family" + }, + "font-size": { + "value": "{rhc.font-size.md.desktop}", + "type": "font-size" + }, + "font-weight": { + "value": "{rhc.font-weight.bold}", + "type": "font-weight" + }, + "line-height": { + "value": "{rhc.line-height.md}", + "type": "line-height" + } + }, + "active": { + "background-color": { + "value": "{rhc.color.grijs.100}", + "type": "color" + } + }, + "hover": { + "background-color": { + "value": "{rhc.color.grijs.50}", + "type": "color" + } + }, + "focus": { + "background-color": { + "value": "{rhc.color.lintblauw.50}", + "type": "color" + } + }, + "background-color": { + "value": "{rhc.color.canvas}", + "type": "color" + }, + "color": { + "value": "{rhc.color.foreground.subdued}", + "type": "color" + }, + "border-width": { + "value": "{rhc.border-width.default}", + "type": "borderWidth" + }, + "border-color": { + "value": "{rhc.color.grijs.300}", + "type": "color" + }, + "heading": { + "color": { + "value": "{rhc.color.foreground.lint}", + "type": "color" + } + }, + "column-gap": { + "value": "{rhc.space.200}", + "type": "space" + }, + "min-height": { + "value": "{rhc.size.target}", + "type": "size" + }, + "padding-block": { + "value": "{rhc.space.150}", + "type": "space" + }, + "padding-inline": { + "value": "{rhc.space.200}", + "type": "space" + } + } + } + } + }, "components/ordered-list": { "utrecht": { "ordered-list": { @@ -6205,6 +6325,7 @@ "components/logo", "components/modal-dialog", "components/nav-bar", + "components/navigation-list", "components/ordered-list", "components/pagination", "components/paragraph",