Skip to content

Commit

Permalink
feat: implement OnyxCard component (#2491)
Browse files Browse the repository at this point in the history
Relates to #2383

Implement basic card component and examples

## Checklist

- [x] The added / edited code has been documented with
[JSDoc](https://jsdoc.app/about-getting-started)
- [ ] All changes are documented in the documentation app (folder
`apps/docs`)
- [x] If a new component is added, at least one [Playwright screenshot
test](https://github.com/SchwarzIT/onyx/actions/workflows/playwright-screenshots.yml)
is added
- [x] A changeset is added with `npx changeset add` if your changes
should be released as npm package (because they affect the library
usage)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
larsrickert and github-actions[bot] authored Jan 13, 2025
1 parent 30d7b4b commit 9ddcd26
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-grapes-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sit-onyx": minor
---

feat: implement `OnyxCard` component
4 changes: 4 additions & 0 deletions apps/demo-app/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OnyxBadge,
OnyxBottomBar,
OnyxButton,
OnyxCard,
OnyxCheckboxGroup,
OnyxDatePicker,
OnyxEmpty,
Expand Down Expand Up @@ -45,6 +46,7 @@ const COMPONENTS = [
"OnyxAvatar",
"OnyxBadge",
"OnyxButton",
"OnyxCard",
"OnyxCheckboxGroup",
"OnyxDatePicker",
"OnyxEmpty",
Expand Down Expand Up @@ -171,6 +173,8 @@ const selectedDate = ref<DateValue>();
<OnyxBadge v-if="show('OnyxBadge')">Badge</OnyxBadge>
<OnyxButton v-if="show('OnyxButton')" label="Button" />

<OnyxCard v-if="show('OnyxCard')"> Card content... </OnyxCard>

<template v-if="show('OnyxCheckboxGroup')">
<OnyxCheckboxGroup
v-model="checkboxState"
Expand Down
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.
26 changes: 26 additions & 0 deletions packages/sit-onyx/src/components/OnyxCard/OnyxCard.ct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import OnyxHeadline from "../OnyxHeadline/OnyxHeadline.vue";
import OnyxCard from "./OnyxCard.vue";

test.describe("Screenshot tests", () => {
executeMatrixScreenshotTest({
name: "Card",
columns: ["default", "clickable"],
rows: ["default", "focus-visible"],
component: (column) => {
return (
<OnyxCard clickable={column === "clickable"} style={{ width: "16rem" }}>
<OnyxHeadline is="h2">Card</OnyxHeadline>
Lorem ipsum dolor sit amet consectetur. Id neque viverra faucibus ullamcorper dui
volutpat.
</OnyxCard>
);
},
hooks: {
beforeEach: async (component, page, column, row) => {
if (row === "focus-visible") await page.keyboard.press("Tab");
},
},
});
});
37 changes: 37 additions & 0 deletions packages/sit-onyx/src/components/OnyxCard/OnyxCard.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { withNativeEventLogging } from "@sit-onyx/storybook-utils";
import type { Meta, StoryObj } from "@storybook/vue3";
import { h } from "vue";
import OnyxCard from "./OnyxCard.vue";

const meta: Meta<typeof OnyxCard> = {
title: "Basic/Card",
component: OnyxCard,
argTypes: {
style: { table: { disable: true } },
default: { control: { disable: true } },
},
};

export default meta;
type Story = StoryObj<typeof OnyxCard>;

export const Default = {
args: {
style: "width: 20rem;",
default: h(
"span",
{ style: "color: var(--onyx-color-text-icons-info-intense);" },
"Note: The card component is fully flexible. It can be adjusted with every content the project needs.",
),
},
} satisfies Story;

export const Clickable = {
argTypes: {
...withNativeEventLogging(["onClick"]),
},
args: {
...Default.args,
clickable: true,
},
} satisfies Story;
58 changes: 58 additions & 0 deletions packages/sit-onyx/src/components/OnyxCard/OnyxCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script lang="ts" setup>
import { useDensity } from "../../composables/density";
import type { OnyxCardProps } from "./types";
const props = withDefaults(defineProps<OnyxCardProps>(), {
clickable: false,
});
defineSlots<{
/**
* Card content.
*/
default(): unknown;
}>();
const { densityClass } = useDensity(props);
</script>

<template>
<component
:is="props.clickable ? 'button' : 'div'"
:class="['onyx-component', 'onyx-card', densityClass, 'onyx-truncation-multiline', 'onyx-text']"
>
<slot></slot>
</component>
</template>

<style lang="scss">
@use "../../styles/mixins/layers.scss";
.onyx-card {
@include layers.component() {
--onyx-card-padding: var(--onyx-density-md);
--onyx-card-gap: var(--onyx-density-xs);
display: flex;
flex-direction: column;
padding: var(--onyx-card-padding);
gap: var(--onyx-card-gap);
border-radius: var(--onyx-radius-sm);
border: var(--onyx-1px-in-rem) solid var(--onyx-color-component-border-neutral);
background-color: var(--onyx-color-base-background-blank);
font-family: var(--onyx-font-family);
color: var(--onyx-color-text-icons-neutral-intense);
max-width: 100%;
&:is(button) {
cursor: pointer;
text-align: initial;
&:focus-visible {
border-color: var(--onyx-color-component-focus-primary);
outline: var(--onyx-outline-width) solid var(--onyx-color-component-focus-primary);
}
}
}
}
</style>
9 changes: 9 additions & 0 deletions packages/sit-onyx/src/components/OnyxCard/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { DensityProp } from "../../composables/density";

export type OnyxCardProps = DensityProp & {
/**
* Whether the whole card should be clickable (will render it as `<button>`).
* **Important**: If clickable, the card must not contain interactive elements like other buttons etc.
*/
clickable?: boolean;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import { h } from "vue";
import OnyxCard from "../../OnyxCard/OnyxCard.vue";
import OnyxHeadline from "../../OnyxHeadline/OnyxHeadline.vue";

const meta: Meta<typeof OnyxCard> = {
title: "Basic/Card/Examples",
component: OnyxCard,
tags: ["!autodocs"],
argTypes: {
style: { table: { disable: true } },
default: { control: { disable: true } },
},
};

export default meta;
type Story = StoryObj<typeof OnyxCard>;

export const HeadlineAndText = {
args: {
style: "width: 20rem;",
default: [
h(OnyxHeadline, { is: "h2" }, "Example headline"),
"Lorem ipsum dolor sit amet consectetur. Id neque viverra faucibus ullamcorper dui volutpat. Vel nec aliquet lorem turpis eu dui. At pellentesque senectus sed volutpat vitae nulla. Nisl cursus dignissim sed eget neque tristique interdum pretium elit.",
],
},
} satisfies Story;

export const ImageCard = {
args: {
style: "width: 20rem;",
default: [
h("img", {
src: "https://picsum.photos/256/128",
alt: "Example image",
style:
"border-radius: var(--onyx-radius-sm); margin-bottom: var(--onyx-card-gap); background-color: var(--onyx-color-base-neutral-200)",
height: 128,
}),
h(OnyxHeadline, { is: "h2" }, "Example headline"),
"Lorem ipsum dolor sit amet consectetur. Id neque viverra faucibus ullamcorper dui volutpat. Vel nec aliquet lorem turpis eu dui. At pellentesque senectus sed volutpat vitae nulla. Nisl cursus dignissim sed eget neque tristique interdum pretium elit.",
],
},
} satisfies Story;
3 changes: 3 additions & 0 deletions packages/sit-onyx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export * from "./components/OnyxBottomBar/types";
export { default as OnyxButton } from "./components/OnyxButton/OnyxButton.vue";
export * from "./components/OnyxButton/types";

export { default as OnyxCard } from "./components/OnyxCard/OnyxCard.vue";
export * from "./components/OnyxCard/types";

export { default as OnyxCheckbox } from "./components/OnyxCheckbox/OnyxCheckbox.vue";
export * from "./components/OnyxCheckbox/types";

Expand Down

0 comments on commit 9ddcd26

Please sign in to comment.