Skip to content

Commit

Permalink
feat(OnyxTabs): support horizontal scrolling when overflowing (#2089)
Browse files Browse the repository at this point in the history
Relates to #2079

- support horizontal scrolling when not all tabs fit into the available
width
- add property `size` to change the font style
- add CSS variable `--onyx-outline-width` and use it in all components
for consistency
- make Storybook examples more realistic
- fix vertical alignment of tab text if its not selected
  • Loading branch information
larsrickert authored Nov 15, 2024
1 parent a0c2d25 commit 3f55c48
Show file tree
Hide file tree
Showing 37 changed files with 218 additions and 73 deletions.
11 changes: 11 additions & 0 deletions .changeset/four-points-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"sit-onyx": minor
---

feat(OnyxTabs): support horizontal scrolling when overflowing

The following additional features/fixes are also included:

- OnyxTabs: add property `size` to change the font style
- OnyxTabs: fix vertical alignment of tab text if its not selected
- add CSS variable `--onyx-outline-width` and use it in all components for consistency
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/sit-onyx/src/components/OnyxButton/OnyxButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ const rippleEvents = computed(() => rippleRef.value?.events ?? {});
}
&:focus-visible {
outline: 0.25rem solid var(--onyx-button-outline-color);
outline: var(--onyx-outline-width) solid var(--onyx-button-outline-color);
}
&:disabled {
Expand Down
23 changes: 2 additions & 21 deletions packages/sit-onyx/src/components/OnyxHeadline/OnyxHeadline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,15 @@ defineSlots<{

<style lang="scss">
@use "../../styles/mixins/layers";
@use "../../styles/mixins/sizes";
.onyx-headline {
@include layers.component() {
font-weight: 600;
font-family: var(--onyx-font-family);
color: var(--onyx-color-text-icons-neutral-intense);
&--h1 {
font-size: 1.75rem;
line-height: 2.5rem;
}
&--h2 {
font-size: 1.25rem;
line-height: 1.75rem;
}
&--h3 {
font-size: 1rem;
line-height: 1.5rem;
}
&--h4,
&--h5,
&--h6 {
font-size: 0.8125rem;
line-height: 1.25rem;
}
@include sizes.define-headline-sizes();
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ $border-radius: var(--onyx-radius-sm);
border: none;
&:focus-visible {
outline: 0.25rem solid var(--onyx-color-base-secondary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-secondary-200);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const isMobile = inject(
outline: 0;
.onyx-user-menu__trigger {
outline: 0.25rem solid var(--onyx-color-base-secondary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-secondary-200);
background-color: var(--onyx-color-base-neutral-200);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,19 @@ const hasReachedMax = computed(() => props.modelValue >= props.pages);
}
&:focus-visible {
$outline-width: 0.25rem;
background-color: var(--onyx-color-base-neutral-200);
outline: $outline-width solid var(--onyx-color-base-primary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-primary-200);
// the right outline of the first button would be cut off / not visible
// so we use this little trick here to add margin-right and reduce the left padding
// of the second button so it does not change in size visually
&:first-of-type {
margin-right: $outline-width;
margin-right: var(--onyx-outline-width);
+ .onyx-pagination__button {
padding-left: calc(var(--onyx-pagination-padding-vertical) - $outline-width);
padding-left: calc(
var(--onyx-pagination-padding-vertical) - var(--onyx-outline-width)
);
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions packages/sit-onyx/src/components/OnyxSelect/OnyxSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -470,10 +470,7 @@ const selectInputProps = computed(() => {
.onyx-select {
@include layers.component() {
$outline-size: 0.25rem;
position: absolute;
visibility: hidden;
opacity: 0;
transition:
Expand All @@ -488,11 +485,11 @@ const selectInputProps = computed(() => {
}
&--top {
bottom: calc(100% + $outline-size);
bottom: calc(100% + var(--onyx-outline-width));
}
&--bottom {
top: calc(100% + $outline-size);
top: calc(100% + var(--onyx-outline-width));
}
&--full {
Expand All @@ -515,7 +512,7 @@ const selectInputProps = computed(() => {
}
&:has(&__wrapper:focus-visible) {
outline: $outline-size solid var(--onyx-color-base-primary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-primary-200);
}
&__wrapper:has(.onyx-mini-search) {
Expand Down
8 changes: 4 additions & 4 deletions packages/sit-onyx/src/components/OnyxSwitch/OnyxSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -296,19 +296,19 @@ $input-width: calc(2 * var(--onyx-switch-icon-size) - 2 * var(--onyx-switch-cont
outline: none;
&:has(.onyx-switch__input:enabled) .onyx-switch__container {
outline: 0.25rem solid var(--onyx-color-base-neutral-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-neutral-200);
}
&:has(.onyx-switch__input:checked:enabled) .onyx-switch__container {
outline: 0.25rem solid var(--onyx-color-base-primary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-primary-200);
}
&:has(.onyx-switch__input:user-invalid:enabled) .onyx-switch__container {
outline: 0.25rem solid var(--onyx-color-base-danger-300);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-danger-300);
}
&:has(.onyx-switch__input:user-invalid:checked:enabled) .onyx-switch__container {
outline: 0.25rem solid var(--onyx-color-base-danger-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-danger-200);
}
}
Expand Down
33 changes: 20 additions & 13 deletions packages/sit-onyx/src/components/OnyxTab/OnyxTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defineSlots<{
const { densityClass } = useDensity(props);
const tabsContext = inject(TABS_INJECTION_KEY);
const skeleton = useSkeletonContext(props);
const sizeClass = computed(() => `onyx-tab--${tabsContext?.size.value}`);
const tab = computed(() =>
tabsContext?.headless.elements.tab.value({
Expand All @@ -35,13 +36,17 @@ const tab = computed(() =>
</script>

<template>
<OnyxSkeleton v-if="skeleton" :class="['onyx-tab-skeleton', densityClass]" v-bind="tab" />
<OnyxSkeleton
v-if="skeleton"
:class="['onyx-tab-skeleton', densityClass, sizeClass]"
v-bind="tab"
/>
<button
v-else
:class="[
'onyx-tab',
'onyx-text--large',
densityClass,
sizeClass,
tab?.['aria-selected'] ? 'onyx-tab--selected' : '',
]"
v-bind="tab"
Expand Down Expand Up @@ -72,12 +77,16 @@ const tab = computed(() =>
<style lang="scss">
@use "../../styles/mixins/layers.scss";
@use "../../styles/mixins/sizes.scss";
.onyx-tab,
.onyx-tab-skeleton {
--onyx-tab-padding-vertical: var(--onyx-density-xs);
--onyx-tab-line-height: 1.75rem;
--onyx-tab-highlight-gap: var(--onyx-density-3xs);
@include layers.component() {
--onyx-tab-padding-vertical: var(--onyx-density-xs);
--onyx-tab-highlight-gap: var(--onyx-density-3xs);
@include sizes.define-headline-sizes();
}
}
.onyx-tab {
Expand All @@ -87,6 +96,9 @@ const tab = computed(() =>
border-radius: var(--onyx-radius-sm);
padding: var(--onyx-tab-padding-vertical) var(--onyx-density-md);
font-weight: 600;
// tabs should have their needed width and be horizontally scrollable instead if they exceed the max parent width
// (will be handled by the OnyxTabs component)
min-width: max-content;
// reset button styles
border: none;
Expand All @@ -105,7 +117,7 @@ const tab = computed(() =>
position: absolute;
left: 50%;
bottom: 0;
bottom: calc(-1 * var(--onyx-tab-highlight-gap));
transform: translateX(-50%);
}
}
Expand All @@ -120,7 +132,7 @@ const tab = computed(() =>
}
&:focus-visible {
outline: 0.25rem solid var(--onyx-color-base-primary-200);
outline: var(--onyx-outline-width) solid var(--onyx-color-base-primary-200);
}
&:active {
Expand All @@ -138,8 +150,6 @@ const tab = computed(() =>
justify-content: center;
gap: var(--onyx-density-xs);
position: relative;
padding-bottom: var(--onyx-tab-highlight-gap);
line-height: var(--onyx-tab-line-height);
}
&__panel {
Expand All @@ -149,10 +159,7 @@ const tab = computed(() =>
&-skeleton {
width: var(--onyx-density-4xl);
height: calc(
var(--onyx-tab-line-height) + 2 * var(--onyx-tab-padding-vertical) +
var(--onyx-tab-highlight-gap)
);
height: calc(1lh + 2 * var(--onyx-tab-padding-vertical) + var(--onyx-tab-highlight-gap));
display: inline-block;
vertical-align: middle;
}
Expand Down
67 changes: 66 additions & 1 deletion packages/sit-onyx/src/components/OnyxTabs/OnyxTabs.ct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ for (const type of ["default", "stretched"] as const) {
modelValue="tab-1"
density={column}
stretched={type === "stretched"}
style={{ width: type === "stretched" ? "40rem" : undefined }}
style={{ width: type === "stretched" ? "24rem" : undefined }}
skeleton={row === "skeleton"}
>
<OnyxTab label="Tab 1" value="tab-1">
Expand Down Expand Up @@ -85,6 +85,71 @@ test.describe("Screenshot tests (custom content)", () => {
});
});

for (const type of ["default", "skeleton"] as const) {
test.describe(`Screenshot tests (sizes, ${type})`, () => {
executeMatrixScreenshotTest({
name: `Tabs (sizes, ${type})`,
columns: DENSITIES,
rows: ["h2", "h3", "h4"],
// TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410
disabledAccessibilityRules: ["color-contrast"],
component: (column, row) => {
return (
<OnyxTabs
label="Example tabs"
modelValue="tab-1"
density={column}
size={row}
skeleton={type === "skeleton"}
>
{Array.from({ length: 3 }, (_, index) => {
const id = index + 1;
return (
<OnyxTab value={`tab-${id}`} label={`Tab ${id}`}>
Panel content {id}...
</OnyxTab>
);
})}
</OnyxTabs>
);
},
});
});
}

test.describe("Screenshot tests (overflow)", () => {
executeMatrixScreenshotTest({
name: "Tabs (overflow)",
columns: ["default"],
rows: ["default", "focus-first", "focus-in-between", "focus-last"],
// TODO: remove when contrast issues are fixed in https://github.com/SchwarzIT/onyx/issues/410
disabledAccessibilityRules: ["color-contrast"],
component: () => {
return (
<OnyxTabs label="Example tabs" modelValue="tab-1" style={{ width: "18rem" }}>
{Array.from({ length: 8 }, (_, index) => {
const id = index + 1;
return (
<OnyxTab value={`tab-${id}`} label={`Tab ${id}`}>
Panel content {id}...
</OnyxTab>
);
})}
</OnyxTabs>
);
},
beforeScreenshot: async (component, page, column, row) => {
if (row === "focus-first") {
await component.getByRole("tab").first().focus();
} else if (row === "focus-last") {
await component.getByRole("tab").last().focus();
} else if (row === "focus-in-between") {
await component.getByRole("tab").nth(4).focus();
}
},
});
});

test("should pass accessibility tests", async ({ mount, makeAxeBuilder, page }) => {
// ARRANGE
const component = await mount(<TestWrapperCt />);
Expand Down
35 changes: 22 additions & 13 deletions packages/sit-onyx/src/components/OnyxTabs/OnyxTabs.stories.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
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";

Expand Down Expand Up @@ -32,25 +30,21 @@ export const Default = {
label: "Example tabs",
modelValue: "tab-1",
default: () => [
h(OnyxTab, { value: "tab-1", label: "Tab 1" }, "Panel content 1..."),
h(OnyxTab, { value: "tab-1", label: "Tab 1" }, () => "Panel content 1..."),
h(OnyxTab, { value: "tab-2", label: "Tab 2" }, () => "Panel content 2..."),
h(
OnyxTab,
{ value: "tab-2" },
{
default: "Panel content 2...",
tab: () => ["Tab 2", h(OnyxBadge, { dot: true, color: "warning" })],
},
{ value: "tab-3", label: "Disabled tab 3", disabled: true },
() => "Panel content 3...",
),
h(
OnyxTab,
{ value: "tab-3", disabled: true },
{ value: "tab-4" },
{
default: "Panel content 3...",
tab: () => [h(OnyxIcon, { icon: placeholder }), "Tab 3 (disabled)"],
default: () => "Panel content 3...",
tab: () => ["Tab 4", h(OnyxBadge, { dot: true, color: "warning" })],
},
),
h(OnyxTab, { value: "tab-4", label: "Tab 4" }, "Panel content 4..."),
h(OnyxTab, { value: "tab-5", skeleton: true, label: "Tab 5" }, "Panel content 5..."),
],
},
} satisfies Story;
Expand All @@ -68,3 +62,18 @@ export const Skeleton = {
skeleton: true,
},
} satisfies Story;

export const ManyTabs = {
args: {
...Default.args,
default: () =>
Array.from({ length: 32 }, (_, index) => {
const id = index + 1;
return h(
OnyxTab,
{ label: `Tab ${id}`, value: `tab-${id}`, skeleton: id === 3 },
() => `Panel content ${id}`,
);
}),
},
} satisfies Story;
Loading

0 comments on commit 3f55c48

Please sign in to comment.