diff --git a/libs/design-system/buttons/navigation-toggle/ng-package.json b/libs/design-system/buttons/navigation-toggle/ng-package.json new file mode 100644 index 0000000..dc244f1 --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/ng-package.json @@ -0,0 +1,7 @@ +{ + "lib": { + "cssUrl": "inline", + "entryFile": "src/index.ts", + "styleIncludePaths": ["../../src/sass"] + } +} diff --git a/libs/design-system/buttons/navigation-toggle/src/index.ts b/libs/design-system/buttons/navigation-toggle/src/index.ts new file mode 100644 index 0000000..bbdd2d1 --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/index.ts @@ -0,0 +1 @@ +export { NavigationToggle } from './lib/navigation-toggle'; diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.html b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.html new file mode 100644 index 0000000..951605e --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.html @@ -0,0 +1,9 @@ + diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.scss b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.scss new file mode 100644 index 0000000..b938abe --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.scss @@ -0,0 +1,49 @@ +@use '@angular/material' as mat; +@use 'internal/token-utils'; +@use './tokens' as *; + +:host { + display: block; + width: fit-content; + + @include mat.button-overrides( + ( + text-container-height: 3rem, + text-label-text-color: token-utils.slot(label-color, $config), + text-icon-spacing: 0.125rem, + text-label-text-font: token-utils.slot(label-font, $config), + text-label-text-size: token-utils.slot(label-font-size, $config), + text-label-text-tracking: token-utils.slot(label-letter-spacing, $config), + text-label-text-weight: token-utils.slot(label-font-weight, $config), + text-with-icon-horizontal-padding: 0.75rem, + text-state-layer-color: transparent, + ) + ); + + @include mat.icon-overrides( + ( + color: token-utils.slot(icon-color, $config), + ) + ); + + .ang-navigation-toggle-button { + &:focus-visible { + outline: solid 0.125rem token-utils.slot(focus-outline-color, $config); + } + + :is(&.ang-navigation-toggle-selected, &:hover, &:active, &:focus-visible) { + .ang-navigation-toggle-text { + text-decoration: underline; + text-underline-offset: 0.25rem; + text-decoration-thickness: 0.125rem; + text-decoration-color: token-utils.slot(underline-color, $config); + } + } + + .ang-navigation-toggle-icon { + height: 1.25rem; + width: 1.25rem; + font-size: 1.25rem; + } + } +} diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.spec.ts b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.spec.ts new file mode 100644 index 0000000..d1cf697 --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.spec.ts @@ -0,0 +1,37 @@ +import { render, screen } from '@testing-library/angular'; +import userEvent from '@testing-library/user-event'; +import { NavigationToggle } from './navigation-toggle'; + +describe('NavigationToggle', () => { + async function setup(link: string | null = null) { + const user = userEvent.setup(); + const rendered = await render(`Category`, { + imports: [NavigationToggle], + componentProperties: { + link, + }, + }); + + return { + user, + ...rendered, + }; + } + + it('toggles state when clicked', async () => { + const { user } = await setup(); + const toggle = screen.getByText('Category').closest('.ang-navigation-toggle-button'); + + expect(toggle).toBeTruthy(); + + const icon = toggle?.querySelector('.ang-navigation-toggle-icon'); + + expect(icon).toHaveAttribute('data-mat-icon-name', 'expand_more'); + + await user.click(toggle as Element); + expect(icon).toHaveAttribute('data-mat-icon-name', 'expand_less'); + + await user.click(toggle as Element); + expect(icon).toHaveAttribute('data-mat-icon-name', 'expand_more'); + }); +}); diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.stories.ts b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.stories.ts new file mode 100644 index 0000000..5437d43 --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.stories.ts @@ -0,0 +1,22 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { NavigationToggle } from './navigation-toggle'; + +const meta: Meta = { + component: NavigationToggle, + title: 'Design System/Buttons/Navigation Toggle', + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/BCEJn9KCIbBJ5MzqnojKQp/AtlasNG-Components?node-id=2101-11132', + }, + }, + render: (args) => ({ + props: args, + template: `Label`, + }), +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.ts b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.ts new file mode 100644 index 0000000..526286d --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/navigation-toggle.ts @@ -0,0 +1,20 @@ +import { ChangeDetectionStrategy, Component, model } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { TrackClick } from '@atlasng/analytics'; + +@Component({ + selector: 'ang-navigation-toggle', + imports: [MatButtonModule, MatIconModule, TrackClick], + templateUrl: './navigation-toggle.html', + styleUrl: './navigation-toggle.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '[class.ang-navigation-toggle-selected]': 'selected()', + '(click)': 'selected.update(s => !s)', + }, +}) +export class NavigationToggle { + /** Whether the button is currently selected */ + readonly selected = model(false); +} diff --git a/libs/design-system/buttons/navigation-toggle/src/lib/tokens.scss b/libs/design-system/buttons/navigation-toggle/src/lib/tokens.scss new file mode 100644 index 0000000..48237ca --- /dev/null +++ b/libs/design-system/buttons/navigation-toggle/src/lib/tokens.scss @@ -0,0 +1,19 @@ +@use 'internal/token-utils'; + +$config: ( + namespace: 'navigation', + tokens: ( + label-color: token-utils.sys-token(on-surface), + icon-color: token-utils.sys-token(on-surface-variant), + underline-color: token-utils.sys-token(primary), + focus-outline-color: token-utils.sys-token(on-surface), + label-font: token-utils.sys-token(title-medium-font), + label-font-size: token-utils.sys-token(title-medium-size), + label-letter-spacing: token-utils.sys-token(title-medium-tracking), + label-font-weight: token-utils.sys-token(title-medium-weight), + ), +); + +@mixin overrides($overrides) { + @include token-utils.apply-overrides($overrides, $config); +} diff --git a/libs/design-system/buttons/navigation/ng-package.json b/libs/design-system/buttons/navigation/ng-package.json new file mode 100644 index 0000000..dc244f1 --- /dev/null +++ b/libs/design-system/buttons/navigation/ng-package.json @@ -0,0 +1,7 @@ +{ + "lib": { + "cssUrl": "inline", + "entryFile": "src/index.ts", + "styleIncludePaths": ["../../src/sass"] + } +} diff --git a/libs/design-system/buttons/navigation/src/index.ts b/libs/design-system/buttons/navigation/src/index.ts new file mode 100644 index 0000000..820fd64 --- /dev/null +++ b/libs/design-system/buttons/navigation/src/index.ts @@ -0,0 +1 @@ +export { Navigation } from './lib/navigation'; diff --git a/libs/design-system/buttons/navigation/src/lib/navigation.html b/libs/design-system/buttons/navigation/src/lib/navigation.html new file mode 100644 index 0000000..4453b9c --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/navigation.html @@ -0,0 +1,11 @@ + + + diff --git a/libs/design-system/buttons/navigation/src/lib/navigation.scss b/libs/design-system/buttons/navigation/src/lib/navigation.scss new file mode 100644 index 0000000..be9c185 --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/navigation.scss @@ -0,0 +1,37 @@ +@use '@angular/material' as mat; +@use 'internal/token-utils'; +@use './tokens' as *; + +:host { + display: block; + width: fit-content; + + @include mat.button-overrides( + ( + text-container-height: 3rem, + text-label-text-color: token-utils.slot(label-color, $config), + text-icon-spacing: 0.125rem, + text-label-text-font: token-utils.slot(label-font, $config), + text-label-text-size: token-utils.slot(label-font-size, $config), + text-label-text-tracking: token-utils.slot(label-letter-spacing, $config), + text-label-text-weight: token-utils.slot(label-font-weight, $config), + text-with-icon-horizontal-padding: 0.75rem, + text-state-layer-color: transparent, + ) + ); + + .ang-navigation-button { + &:focus-visible { + outline: solid 0.125rem token-utils.slot(focus-outline-color, $config); + } + + :is(&:hover, &:active, &:focus-visible) { + .ang-navigation-text { + text-decoration: underline; + text-underline-offset: 0.25rem; + text-decoration-thickness: 0.125rem; + text-decoration-color: token-utils.slot(underline-color, $config); + } + } + } +} diff --git a/libs/design-system/buttons/navigation/src/lib/navigation.spec.ts b/libs/design-system/buttons/navigation/src/lib/navigation.spec.ts new file mode 100644 index 0000000..2f558b4 --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/navigation.spec.ts @@ -0,0 +1,30 @@ +import { render, screen } from '@testing-library/angular'; +import userEvent from '@testing-library/user-event'; +import { Navigation } from './navigation'; + +describe('Navigation', () => { + async function setup(link: string | null = null) { + const user = userEvent.setup(); + + await render(`Go to Docs`, { + imports: [Navigation], + componentProperties: { + link, + }, + }); + + const button = screen.getByRole('link', { name: 'Go to Docs' }); + + return { + user, + button, + }; + } + + it('renders projected content inside the navigation button', async () => { + const { button } = await setup(); + + expect(button).toBeInTheDocument(); + expect(button).toHaveTextContent('Go to Docs'); + }); +}); diff --git a/libs/design-system/buttons/navigation/src/lib/navigation.stories.ts b/libs/design-system/buttons/navigation/src/lib/navigation.stories.ts new file mode 100644 index 0000000..78bfa4d --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/navigation.stories.ts @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { Navigation } from './navigation'; + +const meta: Meta = { + component: Navigation, + title: 'Design System/Buttons/Navigation', + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/BCEJn9KCIbBJ5MzqnojKQp/AtlasNG-Components?node-id=7493-48830', + }, + }, + args: { + link: 'https://example.com', + }, + render: (args) => ({ + props: args, + template: `Label`, + }), +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/libs/design-system/buttons/navigation/src/lib/navigation.ts b/libs/design-system/buttons/navigation/src/lib/navigation.ts new file mode 100644 index 0000000..6f18f71 --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/navigation.ts @@ -0,0 +1,16 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { TrackClick } from '@atlasng/analytics'; +import { AnyLink, AnyLinkCommand } from '@atlasng/common'; + +@Component({ + selector: 'ang-navigation', + imports: [MatButtonModule, AnyLink, TrackClick], + templateUrl: './navigation.html', + styleUrl: './navigation.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Navigation { + /** The link to navigate to */ + readonly link = input(); +} diff --git a/libs/design-system/buttons/navigation/src/lib/tokens.scss b/libs/design-system/buttons/navigation/src/lib/tokens.scss new file mode 100644 index 0000000..48237ca --- /dev/null +++ b/libs/design-system/buttons/navigation/src/lib/tokens.scss @@ -0,0 +1,19 @@ +@use 'internal/token-utils'; + +$config: ( + namespace: 'navigation', + tokens: ( + label-color: token-utils.sys-token(on-surface), + icon-color: token-utils.sys-token(on-surface-variant), + underline-color: token-utils.sys-token(primary), + focus-outline-color: token-utils.sys-token(on-surface), + label-font: token-utils.sys-token(title-medium-font), + label-font-size: token-utils.sys-token(title-medium-size), + label-letter-spacing: token-utils.sys-token(title-medium-tracking), + label-font-weight: token-utils.sys-token(title-medium-weight), + ), +); + +@mixin overrides($overrides) { + @include token-utils.apply-overrides($overrides, $config); +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 62dee4c..d1ad99d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -30,6 +30,10 @@ "@atlasng/design-system": ["./libs/design-system/src/index.ts"], "@atlasng/design-system/buttons/breadcrumbs": ["./libs/design-system/buttons/breadcrumbs/src/index.ts"], "@atlasng/design-system/buttons/help": ["./libs/design-system/buttons/help/src/index.ts"], + "@atlasng/design-system/buttons/navigation": ["./libs/design-system/buttons/navigation/src/index.ts"], + "@atlasng/design-system/buttons/navigation-toggle": [ + "./libs/design-system/buttons/navigation-toggle/src/index.ts" + ], "@atlasng/design-system/buttons/social-media": ["./libs/design-system/buttons/social-media/src/index.ts"], "@atlasng/design-system/indicators/end-of-results": [ "./libs/design-system/indicators/end-of-results/src/index.ts"