diff --git a/.changeset/lucky-pigs-fly.md b/.changeset/lucky-pigs-fly.md new file mode 100644 index 0000000000..93c2506560 --- /dev/null +++ b/.changeset/lucky-pigs-fly.md @@ -0,0 +1,7 @@ +--- +"sit-onyx": minor +--- + +feat: add `OnyxNavBar` component + +If you used one of the `onyx-grid-max-md`, `onyx-grid-max-lg` or `onyx-grid-center` CSS classes which are not placed on the application root, move them to the application root element. See [grid docs](https://onyx.schwarz/development/grid.html#example) for further information diff --git a/.changeset/tall-hounds-count.md b/.changeset/tall-hounds-count.md new file mode 100644 index 0000000000..7e850861fc --- /dev/null +++ b/.changeset/tall-hounds-count.md @@ -0,0 +1,25 @@ +--- +"sit-onyx": major +--- + +rename SCSS breakpoint mixin + +Old: + +```scss +@use "sit-onyx/breakpoints.scss" as onyx; + +@include onyx.breakpoint(max, md) { + // your styles +} +``` + +New: + +```scss +@use "sit-onyx/breakpoints.scss"; + +@include breakpoints.screen(max, md) { + // your styles +} +``` diff --git a/apps/alpha-test-app/package.json b/apps/alpha-test-app/package.json index d55e03bd89..6abe5d7b22 100644 --- a/apps/alpha-test-app/package.json +++ b/apps/alpha-test-app/package.json @@ -10,8 +10,8 @@ "preview": "vite preview" }, "dependencies": { - "sit-onyx": "workspace:^", "@sit-onyx/icons": "workspace:^", + "sit-onyx": "workspace:^", "vue": "^3.4.27", "vue-i18n": "^9.13.1", "vue-router": "^4.3.2" diff --git a/apps/alpha-test-app/src/App.vue b/apps/alpha-test-app/src/App.vue index 02dd6ecdd5..13398b1332 100644 --- a/apps/alpha-test-app/src/App.vue +++ b/apps/alpha-test-app/src/App.vue @@ -1,7 +1,60 @@ diff --git a/apps/alpha-test-app/src/assets/onyx-logo.svg b/apps/alpha-test-app/src/assets/onyx-logo.svg new file mode 100644 index 0000000000..ba24562cae --- /dev/null +++ b/apps/alpha-test-app/src/assets/onyx-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/apps/alpha-test-app/src/views/FormDemoView.vue b/apps/alpha-test-app/src/views/FormDemoView.vue index 2d45a8f5bf..d52010c302 100644 --- a/apps/alpha-test-app/src/views/FormDemoView.vue +++ b/apps/alpha-test-app/src/views/FormDemoView.vue @@ -1,5 +1,5 @@ diff --git a/packages/sit-onyx/src/components/OnyxNavAppArea/types.ts b/packages/sit-onyx/src/components/OnyxNavAppArea/types.ts new file mode 100644 index 0000000000..2bb3729087 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavAppArea/types.ts @@ -0,0 +1,10 @@ +export type OnyxNavAppAreaProps = { + /** + * Logo / app icon URl to display. Slot `logo` can be used to place custom content. + */ + logoUrl?: string; + /** + * Application name to show on the left. + */ + appName?: string; +}; diff --git a/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.ct.tsx b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.ct.tsx new file mode 100644 index 0000000000..6855d0877a --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.ct.tsx @@ -0,0 +1,114 @@ +import { OnyxNavBar, OnyxNavItem } from "../.."; +import { expect, test } from "../../playwright/a11y"; +import { + MOCK_PLAYWRIGHT_LOGO_URL, + defineLogoMockRoutes, + executeMatrixScreenshotTest, +} from "../../playwright/screenshots"; +import { ONYX_BREAKPOINTS } from "../../types"; +import OnyxUserMenu from "../OnyxUserMenu/OnyxUserMenu.vue"; + +test.beforeEach(async ({ page }) => { + await defineLogoMockRoutes(page); +}); + +test.describe("Screenshot tests", () => { + for (const [breakpoint, breakpointWidth] of Object.entries(ONYX_BREAKPOINTS)) { + executeMatrixScreenshotTest({ + name: `Navigation bar (${breakpoint})`, + columns: ["default"], + rows: ["default", "back", "context", "context-back"], + // TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410 + disabledAccessibilityRules: ["color-contrast"], + component: (column, row) => ( + + + + + {row.includes("context") && ( + + )} + + ), + }); + } + + executeMatrixScreenshotTest({ + name: "Navigation bar (lg, grid max-width md)", + columns: ["default"], + rows: ["default", "centered"], + // TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410 + disabledAccessibilityRules: ["color-contrast"], + component: (column, row) => ( + + + + + {row.includes("context") && ( + + )} + + ), + beforeScreenshot: async (component, page) => { + // colorize background so the max-width can be seen easily + await page.addStyleTag({ + content: ` + .onyx-nav-bar { + background-color: var(--onyx-color-base-danger-200); + } + .onyx-nav-bar__content { + background-color: var(--onyx-color-base-background-blank); + } + `, + }); + }, + }); +}); + +test("should behave correctly", async ({ mount }) => { + let appAreaClickEvents = 0; + let backButtonClickEvents = 0; + + let component = await mount( + appAreaClickEvents++} + onBackButtonClick={() => backButtonClickEvents++} + />, + ); + + await component.getByRole("button", { name: "App name" }).click(); + expect(appAreaClickEvents).toBe(1); + + await component.getByRole("button", { name: "Go back" }).click(); + expect(backButtonClickEvents).toBe(1); + + component = await mount( + + + , + ); + + await expect(component.getByRole("button", { name: "Custom app area" })).toBeVisible(); +}); diff --git a/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.stories.ts b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.stories.ts new file mode 100644 index 0000000000..67b406e677 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.stories.ts @@ -0,0 +1,78 @@ +import browserTerminal from "@sit-onyx/icons/browser-terminal.svg?raw"; +import placeholder from "@sit-onyx/icons/placeholder.svg?raw"; +import { defineStorybookActionsAndVModels } from "@sit-onyx/storybook-utils"; +import type { Decorator, Meta, StoryObj } from "@storybook/vue3"; +import { h } from "vue"; +import OnyxBadge from "../OnyxBadge/OnyxBadge.vue"; +import OnyxIcon from "../OnyxIcon/OnyxIcon.vue"; +import OnyxNavItem from "../OnyxNavItem/OnyxNavItem.vue"; +import OnyxNavSeparator from "../OnyxNavSeparator/OnyxNavSeparator.vue"; +import OnyxTag from "../OnyxTag/OnyxTag.vue"; +import { Default as OnyxUserMenuDefault } from "../OnyxUserMenu/OnyxUserMenu.stories"; +import OnyxUserMenu from "../OnyxUserMenu/OnyxUserMenu.vue"; +import OnyxNavBar from "./OnyxNavBar.vue"; + +const meta: Meta = { + title: "components/NavBar", + ...defineStorybookActionsAndVModels({ + component: OnyxNavBar, + events: ["appAreaClick", "backButtonClick"], + argTypes: { + default: { control: { disable: true } }, + contextArea: { control: { disable: true } }, + appArea: { control: { type: "text" } }, + }, + }), +}; + +export default meta; +type Story = StoryObj; + +export const Default = { + args: { + logoUrl: "/onyx-logo.svg", + appName: "App name", + default: () => [ + h(OnyxNavItem, { label: "Item", href: "/", active: true }), + h(OnyxNavItem, { label: "Item", href: "/test" }, () => [ + "Item", + h(OnyxBadge, { dot: true, color: "warning" }), + ]), + h(OnyxNavItem, { label: "Item", href: "https://onyx.schwarz" }), + ], + }, +} satisfies Story; + +export const WithBackButton = { + args: { + ...Default.args, + withBackButton: true, + }, +} satisfies Story; + +export const WithContextArea = { + args: { + ...Default.args, + contextArea: () => [ + h(OnyxTag, { label: "QA stage", color: "warning", icon: browserTerminal }), + h(OnyxNavSeparator), + h(OnyxUserMenu, OnyxUserMenuDefault.args), + ], + }, + decorators: [ + (story) => ({ + components: { story }, + template: `
`, + }), + ] as Decorator[], +} satisfies Story; + +/** + * This example shows a navigation bar with custom app area content. + */ +export const WithCustomAppArea = { + args: { + ...Default.args, + appArea: () => [h(OnyxIcon, { icon: placeholder, color: "secondary" }), "Custom name"], + }, +} satisfies Story; diff --git a/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.vue b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.vue new file mode 100644 index 0000000000..c0d16d55a5 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavBar/OnyxNavBar.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/packages/sit-onyx/src/components/OnyxNavBar/types.ts b/packages/sit-onyx/src/components/OnyxNavBar/types.ts new file mode 100644 index 0000000000..e09ad97843 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavBar/types.ts @@ -0,0 +1,8 @@ +import type { OnyxNavAppAreaProps } from "../OnyxNavAppArea/types"; + +export type OnyxNavBarProps = OnyxNavAppAreaProps & { + /** + * Whether to show a back button. + */ + withBackButton?: boolean; +}; diff --git a/packages/sit-onyx/src/components/OnyxNavItem/OnyxNavItem.stories.ts b/packages/sit-onyx/src/components/OnyxNavItem/OnyxNavItem.stories.ts index 7b14674f4d..37557dcf85 100644 --- a/packages/sit-onyx/src/components/OnyxNavItem/OnyxNavItem.stories.ts +++ b/packages/sit-onyx/src/components/OnyxNavItem/OnyxNavItem.stories.ts @@ -1,8 +1,8 @@ import { defineStorybookActionsAndVModels } from "@sit-onyx/storybook-utils"; import type { Meta, StoryObj } from "@storybook/vue3"; import { h } from "vue"; -import OnyxNavItem from "./OnyxNavItem.vue"; import OnyxBadge from "../OnyxBadge/OnyxBadge.vue"; +import OnyxNavItem from "./OnyxNavItem.vue"; /** * The nav item is used internally to build the main navigation bar component and is not intended to be used individually. @@ -65,11 +65,7 @@ export const WithOptions = { export const WithCustomContent = { args: { ...Default.args, - default: () => - h("div", { style: { gap: "8px", display: "flex", "align-items": "center" } }, [ - "custom label", - h(OnyxBadge, { dot: true }), - ]), + default: () => ["custom label", h(OnyxBadge, { dot: true, variation: "warning" })], }, } satisfies Story; diff --git a/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.ct.tsx b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.ct.tsx new file mode 100644 index 0000000000..113b14ac74 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.ct.tsx @@ -0,0 +1,18 @@ +import { expect, test } from "../../playwright/a11y"; +import { executeMatrixScreenshotTest } from "../../playwright/screenshots"; +import OnyxNavSeparator from "./OnyxNavSeparator.vue"; + +test.describe("Screenshot tests", () => { + executeMatrixScreenshotTest({ + name: "Nav separator", + columns: ["default"], + rows: ["default"], + component: () => , + beforeScreenshot: async (component) => { + await expect(component.getByRole("separator")).toHaveAttribute( + "aria-orientation", + "vertical", + ); + }, + }); +}); diff --git a/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.stories.ts b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.stories.ts new file mode 100644 index 0000000000..a4427b6ba5 --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.stories.ts @@ -0,0 +1,19 @@ +import { defineStorybookActionsAndVModels } from "@sit-onyx/storybook-utils"; +import type { Meta, StoryObj } from "@storybook/vue3"; +import OnyxNavSeparator from "./OnyxNavSeparator.vue"; + +/** + * Separator for the context area of the `OnyxNavBar`. + */ +const meta: Meta = { + title: "support/Separator", + ...defineStorybookActionsAndVModels({ + component: OnyxNavSeparator, + events: [], + }), +}; + +export default meta; +type Story = StoryObj; + +export const Default = { args: {} } satisfies Story; diff --git a/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.vue b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.vue new file mode 100644 index 0000000000..4f2a2c0e8c --- /dev/null +++ b/packages/sit-onyx/src/components/OnyxNavSeparator/OnyxNavSeparator.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/sit-onyx/src/components/OnyxUserMenu/OnyxUserMenu.vue b/packages/sit-onyx/src/components/OnyxUserMenu/OnyxUserMenu.vue index e5ba9ac6c0..e035d12a32 100644 --- a/packages/sit-onyx/src/components/OnyxUserMenu/OnyxUserMenu.vue +++ b/packages/sit-onyx/src/components/OnyxUserMenu/OnyxUserMenu.vue @@ -41,7 +41,7 @@ const avatar = computed(() => { - +