-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement basic
OnyxTabs
component (#2040)
Relates to #2018 Implement basic `OnyxTabs` and `OnyxTab` component.
- Loading branch information
1 parent
5180abb
commit fda8a30
Showing
23 changed files
with
468 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"sit-onyx": minor | ||
--- | ||
|
||
Implement basic OnyxTabs and OnyxTab component. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@sit-onyx/headless": patch | ||
--- | ||
|
||
fix(tabsTesting): correctly test switching to second tab |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+39.4 KB
...laywright/snapshots/components/OnyxTabs/Tabs-custom-content--chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+58.4 KB
...playwright/snapshots/components/OnyxTabs/Tabs-custom-content--firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+60.5 KB
.../playwright/snapshots/components/OnyxTabs/Tabs-custom-content--webkit-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+37.1 KB
...-onyx/playwright/snapshots/components/OnyxTabs/Tabs-default--chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+50.4 KB
...t-onyx/playwright/snapshots/components/OnyxTabs/Tabs-default--firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+60.1 KB
...it-onyx/playwright/snapshots/components/OnyxTabs/Tabs-default--webkit-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+47.6 KB
...nyx/playwright/snapshots/components/OnyxTabs/Tabs-stretched--chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+79.3 KB
...onyx/playwright/snapshots/components/OnyxTabs/Tabs-stretched--firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+51.8 KB
...-onyx/playwright/snapshots/components/OnyxTabs/Tabs-stretched--webkit-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions
24
packages/sit-onyx/src/components/OnyxTab/OnyxTab.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type { Meta, StoryObj } from "@storybook/vue3"; | ||
import OnyxTab from "./OnyxTab.vue"; | ||
|
||
/** | ||
* A single tab component. Only intended to be used with the [OnyxTabs](/docs/navigation-tabs--docs) component. | ||
*/ | ||
const meta: Meta<typeof OnyxTab> = { | ||
title: "Support/Tab", | ||
component: OnyxTab, | ||
argTypes: { | ||
tab: { control: { disable: true } }, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof OnyxTab>; | ||
|
||
export const Default = { | ||
args: { | ||
value: "tab-1", | ||
label: "Tab 1", | ||
default: "Panel content 1...", | ||
}, | ||
} satisfies Story; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<script lang="ts" setup> | ||
import { computed, inject } from "vue"; | ||
import { useDensity } from "../../composables/density"; | ||
import { TABS_INJECTION_KEY } from "../OnyxTabs/types"; | ||
import type { OnyxTabProps } from "./types"; | ||
const props = defineProps<OnyxTabProps>(); | ||
defineSlots<{ | ||
/** | ||
* Tab panel / content. | ||
*/ | ||
default(): unknown; | ||
/** | ||
* Optional slot to override the tab content. By default, the `label` property will be displayed. | ||
*/ | ||
tab?(): unknown; | ||
}>(); | ||
const { densityClass } = useDensity(props); | ||
const tabsContext = inject(TABS_INJECTION_KEY); | ||
const tab = computed(() => tabsContext?.headless.elements.tab.value({ value: props.value })); | ||
</script> | ||
|
||
<template> | ||
<button | ||
:class="[ | ||
'onyx-tab', | ||
'onyx-text--large', | ||
densityClass, | ||
tab?.['aria-selected'] ? 'onyx-tab--selected' : '', | ||
]" | ||
v-bind="tab" | ||
type="button" | ||
> | ||
<div class="onyx-tab__label"> | ||
<slot name="tab">{{ props.label }}</slot> | ||
</div> | ||
</button> | ||
<!-- The <Teleport> is used because we want to offer a nice API for the user | ||
so he can provide both tab and the panel content in one "OnyxTab" component. | ||
However, for the accessibility pattern (see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/), | ||
we need a separated HTML structure where the tab and the panel must not be nested. | ||
The <Teleport> will allow us to achieve this by moving the panel content to the `OnyxTabs` component. | ||
--> | ||
<Teleport :to="tabsContext?.panelRef.value" :disabled="!tabsContext?.panelRef.value" defer> | ||
<div | ||
v-if="tab?.['aria-selected']" | ||
v-bind="tabsContext?.headless.elements.tabpanel.value({ value: props.value })" | ||
class="onyx-tab__panel" | ||
> | ||
<slot></slot> | ||
</div> | ||
</Teleport> | ||
</template> | ||
<style lang="scss"> | ||
@use "../../styles/mixins/layers.scss"; | ||
.onyx-tab { | ||
@include layers.component() { | ||
font-family: var(--onyx-font-family); | ||
color: var(--onyx-color-text-icons-neutral-medium); | ||
border-radius: var(--onyx-radius-sm); | ||
padding: var(--onyx-density-xs) var(--onyx-density-md); | ||
cursor: pointer; | ||
font-weight: 600; | ||
// reset button styles | ||
border: none; | ||
background-color: transparent; | ||
&--selected { | ||
color: var(--onyx-color-text-icons-neutral-intense); | ||
.onyx-tab__label { | ||
&::after { | ||
content: ""; | ||
height: 0.125rem; | ||
background-color: var(--onyx-color-base-primary-500); | ||
width: calc(100% - 2 * var(--onyx-density-xs)); | ||
min-width: 1rem; | ||
position: absolute; | ||
left: 50%; | ||
bottom: 0; | ||
transform: translateX(-50%); | ||
} | ||
} | ||
} | ||
&:hover, | ||
&:focus-visible { | ||
background-color: var(--onyx-color-base-neutral-200); | ||
} | ||
&:focus-visible { | ||
outline: 0.25rem solid var(--onyx-color-base-primary-200); | ||
} | ||
&:active { | ||
color: var(--onyx-color-text-icons-primary-bold); | ||
} | ||
&__label { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
gap: var(--onyx-density-xs); | ||
position: relative; | ||
padding-bottom: var(--onyx-density-3xs); | ||
} | ||
&__panel { | ||
font-family: var(--onyx-font-family); | ||
color: var(--onyx-color-text-icons-neutral-intense); | ||
} | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type { DensityProp } from "../../composables/density"; | ||
|
||
export type OnyxTabProps = DensityProp & { | ||
/** | ||
* Value of the tab when its selected. Will be the `modelValue` / `v-model` of the `OnyxTabs` component. | ||
*/ | ||
value: PropertyKey; | ||
/** | ||
* Tab label to display. Alternatively, the `tab` slot can be used. | ||
*/ | ||
label?: string; | ||
}; |
101 changes: 101 additions & 0 deletions
101
packages/sit-onyx/src/components/OnyxTabs/OnyxTabs.ct.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { tabsTesting } from "@sit-onyx/headless/playwright"; | ||
import { DENSITIES } from "../../composables/density"; | ||
import { expect, test } from "../../playwright/a11y"; | ||
import { executeMatrixScreenshotTest, mockPlaywrightIcon } from "../../playwright/screenshots"; | ||
import OnyxBadge from "../OnyxBadge/OnyxBadge.vue"; | ||
import OnyxIcon from "../OnyxIcon/OnyxIcon.vue"; | ||
import OnyxTab from "../OnyxTab/OnyxTab.vue"; | ||
import OnyxTabs from "./OnyxTabs.vue"; | ||
import TestWrapperCt from "./TestWrapper.ct.vue"; | ||
|
||
for (const type of ["default", "stretched"] as const) { | ||
test.describe(`Screenshot tests (${type})`, () => { | ||
executeMatrixScreenshotTest({ | ||
name: `Tabs (${type})`, | ||
columns: DENSITIES, | ||
rows: ["default", "hover", "active", "focus-visible"], | ||
// TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410 | ||
disabledAccessibilityRules: ["color-contrast"], | ||
component: (column) => { | ||
return ( | ||
<OnyxTabs | ||
label="Example tabs" | ||
modelValue="tab-1" | ||
density={column} | ||
stretched={type === "stretched"} | ||
style={{ width: type === "stretched" ? "24rem" : undefined }} | ||
> | ||
<OnyxTab label="Tab 1" value="tab-1"> | ||
Panel content 1... | ||
</OnyxTab> | ||
<OnyxTab label="Tab 2" value="tab-2"> | ||
Panel content 2... | ||
</OnyxTab> | ||
</OnyxTabs> | ||
); | ||
}, | ||
beforeScreenshot: async (component, page, column, row) => { | ||
const tab1 = component.getByRole("tab", { name: "Tab 1" }); | ||
if (row === "hover") await tab1.hover(); | ||
if (row === "focus-visible") await page.keyboard.press("Tab"); | ||
if (row === "active") { | ||
const box = (await tab1.boundingBox())!; | ||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); | ||
await page.mouse.down(); | ||
} | ||
}, | ||
}); | ||
}); | ||
} | ||
|
||
test.describe("Screenshot tests (custom content)", () => { | ||
executeMatrixScreenshotTest({ | ||
name: "Tabs (custom content)", | ||
columns: DENSITIES, | ||
rows: ["icon", "badge", "icon-badge"], | ||
component: (column, row) => { | ||
return ( | ||
<OnyxTabs label="Example tabs" modelValue="tab-1" density={column}> | ||
<OnyxTab value="tab-1"> | ||
<span>Panel content 1...</span> | ||
|
||
<template v-slot:tab> | ||
{row.includes("icon") && <OnyxIcon icon={mockPlaywrightIcon} />} | ||
<span>Tab 1</span> | ||
{row.includes("badge") && <OnyxBadge color="warning" dot />} | ||
</template> | ||
</OnyxTab> | ||
|
||
<OnyxTab value="tab-2"> | ||
<span>Panel content 2...</span> | ||
|
||
<template v-slot:tab> | ||
{row.includes("icon") && <OnyxIcon icon={mockPlaywrightIcon} />} | ||
<span>Tab 2</span> | ||
{row.includes("badge") && <OnyxBadge color="warning" dot />} | ||
</template> | ||
</OnyxTab> | ||
</OnyxTabs> | ||
); | ||
}, | ||
}); | ||
}); | ||
|
||
test("should pass accessibility tests", async ({ mount, makeAxeBuilder, page }) => { | ||
// ARRANGE | ||
const component = await mount(<TestWrapperCt />); | ||
|
||
// ACT | ||
const accessibilityScanResults = await makeAxeBuilder() | ||
// TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410 | ||
.disableRules(["color-contrast"]) | ||
.analyze(); | ||
|
||
// ASSERT | ||
expect(accessibilityScanResults.violations).toEqual([]); | ||
|
||
await tabsTesting({ | ||
page, | ||
tablist: component.getByRole("tablist"), | ||
}); | ||
}); |
51 changes: 51 additions & 0 deletions
51
packages/sit-onyx/src/components/OnyxTabs/OnyxTabs.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import placeholder from "@sit-onyx/icons/placeholder.svg?raw"; | ||
import type { Meta, StoryObj } from "@storybook/vue3"; | ||
import { h } from "vue"; | ||
import OnyxBadge from "../OnyxBadge/OnyxBadge.vue"; | ||
import OnyxIcon from "../OnyxIcon/OnyxIcon.vue"; | ||
import OnyxTab from "../OnyxTab/OnyxTab.vue"; | ||
import OnyxTabs from "./OnyxTabs.vue"; | ||
|
||
const meta: Meta<typeof OnyxTabs> = { | ||
title: "Navigation/Tabs", | ||
component: OnyxTabs, | ||
argTypes: { | ||
default: { control: { disable: true } }, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof OnyxTabs>; | ||
|
||
export const Default = { | ||
args: { | ||
label: "Example tabs", | ||
modelValue: "tab-1", | ||
default: () => [ | ||
h(OnyxTab, { value: "tab-1", label: "Tab 1" }, "Panel content 1..."), | ||
h( | ||
OnyxTab, | ||
{ value: "tab-2" }, | ||
{ | ||
default: "Panel content 2...", | ||
tab: () => ["Tab 2", h(OnyxBadge, { dot: true, color: "warning" })], | ||
}, | ||
), | ||
h( | ||
OnyxTab, | ||
{ value: "tab-3" }, | ||
{ | ||
default: "Panel content 3...", | ||
tab: () => [h(OnyxIcon, { icon: placeholder }), "Tab 3"], | ||
}, | ||
), | ||
], | ||
}, | ||
} satisfies Story; | ||
|
||
export const Stretched = { | ||
args: { | ||
...Default.args, | ||
stretched: true, | ||
}, | ||
} satisfies Story; |
Oops, something went wrong.