diff --git a/packages/ods/react/tests/_app/src/components.ts b/packages/ods/react/tests/_app/src/components.ts index 1d4cd35ad1..a42d6746b6 100644 --- a/packages/ods/react/tests/_app/src/components.ts +++ b/packages/ods/react/tests/_app/src/components.ts @@ -35,6 +35,7 @@ const componentNames = [ 'checkbox', 'select', 'modal', + 'card', //--generator-anchor-- ]; diff --git a/packages/ods/react/tests/_app/src/components/ods-card.tsx b/packages/ods/react/tests/_app/src/components/ods-card.tsx new file mode 100644 index 0000000000..91ccd228c9 --- /dev/null +++ b/packages/ods/react/tests/_app/src/components/ods-card.tsx @@ -0,0 +1,12 @@ +import React from 'react-dom/client'; +import { OdsCard, OdsText } from 'ods-components-react'; + +const Card = () => { + return ( + + Hello, world! + + ); +}; + +export default Card; diff --git a/packages/ods/react/tests/e2e/ods-card.e2e.ts b/packages/ods/react/tests/e2e/ods-card.e2e.ts new file mode 100644 index 0000000000..3d1a129b90 --- /dev/null +++ b/packages/ods/react/tests/e2e/ods-card.e2e.ts @@ -0,0 +1,23 @@ +import type { Page } from 'puppeteer'; +import { goToComponentPage, setupBrowser } from '../setup'; + +describe('ods-card react', () => { + const setup = setupBrowser(); + let page: Page; + + beforeAll(async () => { + page = setup().page; + }); + + beforeEach(async () => { + await goToComponentPage(page, 'ods-card'); + }); + + it('render the component correctly', async () => { + const elem = await page.$('ods-card'); + const boundingBox = await elem?.boundingBox(); + + expect(boundingBox?.height).toBeGreaterThan(0); + expect(boundingBox?.width).toBeGreaterThan(0); + }); +}); diff --git a/packages/ods/src/components/card/.gitignore b/packages/ods/src/components/card/.gitignore new file mode 100644 index 0000000000..7b15d7273d --- /dev/null +++ b/packages/ods/src/components/card/.gitignore @@ -0,0 +1,5 @@ +# Local Stencil command generates external ods component build at the root of the project +# Excluding them is a temporary solution to avoid pushing generated files +# But the issue may cause main build (ods-component package) to fails, as it detects multiples occurences +# of the same component and thus you have to delete all those generated dir manually +*/src/ diff --git a/packages/ods/src/components/card/package.json b/packages/ods/src/components/card/package.json new file mode 100644 index 0000000000..61f9cb2fae --- /dev/null +++ b/packages/ods/src/components/card/package.json @@ -0,0 +1,19 @@ +{ + "name": "@ovhcloud/ods-component-card", + "version": "17.1.0", + "private": true, + "description": "ODS Card component", + "main": "dist/index.cjs.js", + "collection": "dist/collection/collection-manifest.json", + "scripts": { + "clean": "rimraf .stencil coverage dist docs-api www", + "doc": "typedoc --pretty --plugin ../../../scripts/typedoc-plugin-decorator.js && node ../../../scripts/generate-typedoc-md.js", + "lint:scss": "stylelint 'src/components/**/*.scss'", + "lint:ts": "eslint '{src,tests}/**/*.{js,ts,tsx}'", + "start": "stencil build --dev --watch --serve", + "test:e2e": "stencil test --e2e --config stencil.config.ts", + "test:e2e:ci": "tsc --noEmit && stencil test --e2e --ci --runInBand --config stencil.config.ts", + "test:spec": "stencil test --spec --config stencil.config.ts --coverage", + "test:spec:ci": "tsc --noEmit && stencil test --config stencil.config.ts --spec --ci --coverage" + } +} diff --git a/packages/ods/src/components/card/src/components/ods-card/ods-card.scss b/packages/ods/src/components/card/src/components/ods-card/ods-card.scss new file mode 100644 index 0000000000..0c5c6adf1f --- /dev/null +++ b/packages/ods/src/components/card/src/components/ods-card/ods-card.scss @@ -0,0 +1,18 @@ +:host(.ods-card) { + display: inline-block; +} + +.ods-card { + &__wrapper { + border: 1px solid; + border-radius: 8px; + + &--neutral { + border-color: var(--ods-color-neutral-200); + } + + &--primary { + border-color: var(--ods-color-primary-200); + } + } +} \ No newline at end of file diff --git a/packages/ods/src/components/card/src/components/ods-card/ods-card.tsx b/packages/ods/src/components/card/src/components/ods-card/ods-card.tsx new file mode 100644 index 0000000000..a3fcf38e25 --- /dev/null +++ b/packages/ods/src/components/card/src/components/ods-card/ods-card.tsx @@ -0,0 +1,22 @@ +import type { FunctionalComponent } from '@stencil/core'; +import { Component, Host, Prop, h } from '@stencil/core'; +import { ODS_CARD_COLOR, type OdsCardColor } from '../../constants/card-color'; + +@Component({ + shadow: true, + styleUrl: 'ods-card.scss', + tag: 'ods-card', +}) +export class OdsCard { + @Prop({ reflect: true }) public color: OdsCardColor = ODS_CARD_COLOR.primary; + + render(): FunctionalComponent { + return ( + +
+ +
+
+ ); + } +} diff --git a/packages/ods/src/components/card/src/constants/card-color.ts b/packages/ods/src/components/card/src/constants/card-color.ts new file mode 100644 index 0000000000..8b830fd674 --- /dev/null +++ b/packages/ods/src/components/card/src/constants/card-color.ts @@ -0,0 +1,14 @@ +enum ODS_CARD_COLOR { + neutral = 'neutral', + primary = 'primary', +} + +type OdsCardColor = `${ODS_CARD_COLOR}`; + +const ODS_CARD_COLORS = Object.freeze(Object.values(ODS_CARD_COLOR)); + +export { + ODS_CARD_COLOR, + ODS_CARD_COLORS, + type OdsCardColor, +}; \ No newline at end of file diff --git a/packages/ods/src/components/card/src/globals.ts b/packages/ods/src/components/card/src/globals.ts new file mode 100644 index 0000000000..a4fcf2c6ab --- /dev/null +++ b/packages/ods/src/components/card/src/globals.ts @@ -0,0 +1,9 @@ +/** + * Import here all the external ODS component that you need to run the current component + * when running dev server (yarn start) or e2e tests + * + * ex: + * import '../../text/src'; + */ + +import '../../text/src'; \ No newline at end of file diff --git a/packages/ods/src/components/card/src/index.html b/packages/ods/src/components/card/src/index.html new file mode 100644 index 0000000000..a61c5f1020 --- /dev/null +++ b/packages/ods/src/components/card/src/index.html @@ -0,0 +1,39 @@ + + + + + + Dev ods-card + + + + + + + +

Default

+ + Hello, world! + + +

Primary

+ + Hello, world! + + +

Neutral

+ + Hello, world! + + + + + diff --git a/packages/ods/src/components/card/src/index.ts b/packages/ods/src/components/card/src/index.ts new file mode 100644 index 0000000000..d54b53406b --- /dev/null +++ b/packages/ods/src/components/card/src/index.ts @@ -0,0 +1,2 @@ +export { OdsCard } from './components/ods-card/ods-card'; +export { ODS_CARD_COLOR, ODS_CARD_COLORS, type OdsCardColor } from './constants/card-color'; \ No newline at end of file diff --git a/packages/ods/src/components/card/stencil.config.ts b/packages/ods/src/components/card/stencil.config.ts new file mode 100644 index 0000000000..f68caa4aab --- /dev/null +++ b/packages/ods/src/components/card/stencil.config.ts @@ -0,0 +1,7 @@ +import { getStencilConfig } from '../../config/stencil'; + +export const config = getStencilConfig({ + args: process.argv.slice(2), + componentCorePackage: '@ovhcloud/ods-component-card', + namespace: 'ods-card', +}); diff --git a/packages/ods/src/components/card/tests/rendering/ods-card.e2e.ts b/packages/ods/src/components/card/tests/rendering/ods-card.e2e.ts new file mode 100644 index 0000000000..38f32c942c --- /dev/null +++ b/packages/ods/src/components/card/tests/rendering/ods-card.e2e.ts @@ -0,0 +1,56 @@ +import type { E2EElement, E2EPage } from '@stencil/core/testing'; +import { newE2EPage } from '@stencil/core/testing'; + +describe('ods-card rendering', () => { + let el: E2EElement; + let page: E2EPage; + + async function setup(content: string, customStyle?: string): Promise { + page = await newE2EPage(); + + await page.setContent(content); + await page.evaluate(() => document.body.style.setProperty('margin', '0px')); + + if (customStyle) { + await page.addStyleTag({ content: customStyle }); + } + + el = await page.find('ods-card'); + } + + it('should render the web component', async() => { + await setup(''); + + expect(el.shadowRoot).not.toBeNull(); + }); + + describe('color', () => { + it('should render with correct color primary', async() => { + await setup(` + + + `); + + const hasClassPrimary = await page.evaluate(() => { + const wrapper = document.querySelector('ods-card')?.shadowRoot?.querySelector('.ods-card__wrapper'); + return wrapper?.classList.contains('ods-card__wrapper--primary'); + }); + + expect(hasClassPrimary).toBe(true); + }); + + it('should render with correct color neutral', async() => { + await setup(` + + + `); + + const hasClassNeutral = await page.evaluate(() => { + const wrapper = document.querySelector('ods-card')?.shadowRoot?.querySelector('.ods-card__wrapper'); + return wrapper?.classList.contains('ods-card__wrapper--neutral'); + }); + + expect(hasClassNeutral).toBe(true); + }); + }); +}); diff --git a/packages/ods/src/components/card/tests/rendering/ods-card.spec.ts b/packages/ods/src/components/card/tests/rendering/ods-card.spec.ts new file mode 100644 index 0000000000..a4780879e1 --- /dev/null +++ b/packages/ods/src/components/card/tests/rendering/ods-card.spec.ts @@ -0,0 +1,35 @@ +import type { SpecPage } from '@stencil/core/testing'; +import { newSpecPage } from '@stencil/core/testing'; +import { ODS_CARD_COLOR, OdsCard } from '../../src'; + +describe('ods-card rendering', () => { + let page: SpecPage; + let root: HTMLElement | undefined; + + async function setup(html: string): Promise { + page = await newSpecPage({ + components: [OdsCard], + html, + }); + + root = page.root; + } + + describe('attributes', () => { + describe('color', () => { + it('should be reflected', async() => { + const colorValue = ODS_CARD_COLOR.neutral; + + await setup(``); + + expect(root?.getAttribute('color')).toBe(colorValue); + }); + + it('should be set to its default value', async() => { + await setup(''); + + expect(root?.getAttribute('color')).toBe(ODS_CARD_COLOR.primary); + }); + }); + }); +}); diff --git a/packages/ods/src/components/card/tsconfig.json b/packages/ods/src/components/card/tsconfig.json new file mode 100644 index 0000000000..e242da5e2f --- /dev/null +++ b/packages/ods/src/components/card/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "include": [ + "src", + "tests" + ] +} diff --git a/packages/ods/src/components/card/typedoc.json b/packages/ods/src/components/card/typedoc.json new file mode 100644 index 0000000000..74d6700e67 --- /dev/null +++ b/packages/ods/src/components/card/typedoc.json @@ -0,0 +1,10 @@ +{ + "entryPoints": ["src/index.ts"], + "excludeInternal": true, + "excludePrivate": true, + "excludeProtected": true, + "hideGenerator": true, + "json": "dist/docs-api/typedoc.json", + "out": "dist/docs-api/", + "tsconfig":"tsconfig.json" +} diff --git a/packages/ods/src/components/index.ts b/packages/ods/src/components/index.ts index 3ca65a17ca..2d80a06864 100644 --- a/packages/ods/src/components/index.ts +++ b/packages/ods/src/components/index.ts @@ -33,4 +33,5 @@ export * from './message/src'; export * from './radio/src'; export * from './checkbox/src'; export * from './select/src'; -export * from './modal/src'; \ No newline at end of file +export * from './modal/src'; +export * from './card/src'; \ No newline at end of file diff --git a/packages/ods/vue/tests/_app/src/components.ts b/packages/ods/vue/tests/_app/src/components.ts index 1d4cd35ad1..a42d6746b6 100644 --- a/packages/ods/vue/tests/_app/src/components.ts +++ b/packages/ods/vue/tests/_app/src/components.ts @@ -35,6 +35,7 @@ const componentNames = [ 'checkbox', 'select', 'modal', + 'card', //--generator-anchor-- ]; diff --git a/packages/ods/vue/tests/_app/src/components/ods-card.vue b/packages/ods/vue/tests/_app/src/components/ods-card.vue new file mode 100644 index 0000000000..4d4f55097e --- /dev/null +++ b/packages/ods/vue/tests/_app/src/components/ods-card.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/ods/vue/tests/e2e/ods-card.e2e.ts b/packages/ods/vue/tests/e2e/ods-card.e2e.ts new file mode 100644 index 0000000000..1c0935855c --- /dev/null +++ b/packages/ods/vue/tests/e2e/ods-card.e2e.ts @@ -0,0 +1,23 @@ +import type { Page } from 'puppeteer'; +import { goToComponentPage, setupBrowser } from '../setup'; + +describe('ods-card vue', () => { + const setup = setupBrowser(); + let page: Page; + + beforeAll(async () => { + page = setup().page; + }); + + beforeEach(async () => { + await goToComponentPage(page, 'ods-card'); + }); + + it('render the component correctly', async () => { + const elem = await page.$('ods-card'); + const boundingBox = await elem?.boundingBox(); + + expect(boundingBox?.height).toBeGreaterThan(0); + expect(boundingBox?.width).toBeGreaterThan(0); + }); +}); diff --git a/packages/storybook/stories/components/card/card.stories.ts b/packages/storybook/stories/components/card/card.stories.ts new file mode 100644 index 0000000000..6e7efc0276 --- /dev/null +++ b/packages/storybook/stories/components/card/card.stories.ts @@ -0,0 +1,108 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { defineCustomElement } from '@ovhcloud/ods-components/dist/components/ods-card'; +import { ODS_CARD_COLOR, ODS_CARD_COLORS } from '@ovhcloud/ods-components'; +import { html } from 'lit-html'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; +import { CONTROL_CATEGORY, orderControls } from '../../control'; + +defineCustomElement(); + +const meta: Meta = { + title: 'ODS Components/Layout/Card', + component: 'ods-card', +}; + +export default meta; + +export const Demo: StoryObj = { + render: (args) => html` + + ${unsafeHTML(args.content)} + + + + `, + argTypes: orderControls({ + color: { + table: { + category: CONTROL_CATEGORY.design, + defaultValue: { summary: ODS_CARD_COLOR.primary }, + type: { summary: ODS_CARD_COLORS }, + }, + control: { type: 'select' }, + options: ODS_CARD_COLORS, + }, + content: { + table: { + category: CONTROL_CATEGORY.slot, + defaultValue: { summary: 'ø' }, + }, + control: 'text', + }, + customCss: { + table: { + category: CONTROL_CATEGORY.design, + defaultValue: { summary: 'ø' }, + }, + control: 'text', + description: 'Set a custom style properties. Example: ".demo-card::part(card) { border: 1px red solid; }"', + }, + }), + args: { + color: ODS_CARD_COLOR.primary, + content: 'Hello, world!', + customCss: '.demo-card::part(card) { padding: 0 24px; }', + }, +}; + +export const Default: StoryObj = { + tags: ['isHidden'], + render: () => html` + + `, +}; + + +export const Overview: StoryObj = { + tags: ['isHidden'], + render: () => html` + + Hello, world! + + + + `, +}; + +export const CustomCSS: StoryObj = { + tags: ['isHidden'], + render: () => html` + + Hello, world! + + + + `, +}; + +export const Color: StoryObj = { + tags: ['isHidden'], + render: () => html` + + Hello, world! + + `, +}; \ No newline at end of file diff --git a/packages/storybook/stories/components/card/documentation.mdx b/packages/storybook/stories/components/card/documentation.mdx new file mode 100644 index 0000000000..a334dda9d7 --- /dev/null +++ b/packages/storybook/stories/components/card/documentation.mdx @@ -0,0 +1,39 @@ +import { Canvas, Meta, Markdown } from '@storybook/blocks'; +import SpecificationsCard from '@ovhcloud/ods-components/src/components/card/documentation/spec.md?raw'; +import { Banner } from '../../banner'; +import { DocNavigator } from '../../doc-navigator'; +import * as CardStories from './card.stories'; +import { LINK_ID } from '../../zeroheight'; + + + + + +# Overview + +Card is a component designed to be a container for displaying content and other elements to communicate information or feature a call-to-action within a page. + + + + + +{ SpecificationsCard } + +# Style customization + +You can add your own style on the card element using the part `card`. + +Custom Card CSS: + + + +# Examples + +## Default + + + +## Color + + +... diff --git a/packages/storybook/stories/components/card/migration.from.17.x.mdx b/packages/storybook/stories/components/card/migration.from.17.x.mdx new file mode 100644 index 0000000000..0bf98e3c3c --- /dev/null +++ b/packages/storybook/stories/components/card/migration.from.17.x.mdx @@ -0,0 +1,11 @@ +import { Meta } from '@storybook/blocks'; +import * as CardStories from './card.stories'; + + + +# Card - migrate from v17 to v18 +---- + +This component has been first released with the v18, there is no migration involved coming from v17.x. + +To learn how to use this component, please check the [documentation](?path=/docs/ods-components-layout-card--documentation). \ No newline at end of file diff --git a/packages/storybook/stories/zeroheight.ts b/packages/storybook/stories/zeroheight.ts index c51623c9e7..840c90b106 100644 --- a/packages/storybook/stories/zeroheight.ts +++ b/packages/storybook/stories/zeroheight.ts @@ -7,6 +7,7 @@ enum LINK_ID { BADGE = '879fff-badge', BREADCRUMB = '6707ac-breadcrumb', BUTTON = '794ee9-button', + CARD = '76f7e4-card', CHECKBOX_BUTTON = '0166c5-checkbox-button', CLIPBOARD = '25bcae-clipboard', CODE = '07c385-code', diff --git a/yarn.lock b/yarn.lock index c870a72fdf..9800f1841e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3940,11 +3940,20 @@ __metadata: languageName: unknown linkType: soft +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> ca6158dbb (feat(button): implement component) ======= ======= +======= +"@ovhcloud/ods-component-card@workspace:packages/ods/src/components/card": + version: 0.0.0-use.local + resolution: "@ovhcloud/ods-component-card@workspace:packages/ods/src/components/card" + languageName: unknown + linkType: soft + +>>>>>>> 085d24bf8 (feat(card): implement component) "@ovhcloud/ods-component-checkbox@workspace:packages/ods/src/components/checkbox": version: 0.0.0-use.local resolution: "@ovhcloud/ods-component-checkbox@workspace:packages/ods/src/components/checkbox"