diff --git a/@xen-orchestra/web-core/docs/component-definition.md b/@xen-orchestra/web-core/docs/component-definition.md deleted file mode 100644 index 24270406ee5..00000000000 --- a/@xen-orchestra/web-core/docs/component-definition.md +++ /dev/null @@ -1,268 +0,0 @@ -# Component definition - -Lexicon: - -- DS: Design System -- SFC: Single-File Component -- Component: A Vue component (being defined in the DS or not) -- DS Component: A component specifically defined in the Design System (DS) -- Subcomponent: A component that is part of a Component or a DS Component - -## Components and Subcomponents MUST be defined as Vue SFC (Single-File Component) - -## DS Components MUST be stored in their own directory. - -## Directory name MUST be in kebab-case (e.g. `my-component`) - -## Component name MUST be in PascalCase - -## DS Component/Subcomponent name MUST start with `Vts` (e.g. `VtsMyComponent.vue`) - -❌ Bad - -`components/Square.vue` - -✅ Good - -`components/square/VtsSquare.vue` - -## Components SHOULD be kept short and be split into multiple subcomponents if necessary, stored in the same directory as the main component. - -❌ Bad - -``` -/components/ - /square/ - /VtsSquare.vue - /square-icon/ - /VtsSquareIcon.vue <- This component is not part of the DS and will be used only in Square.vue -``` - -✅ Good - -``` -/components/ - /square/ - /VtsSquare.vue - /VtsSquareIcon.vue -``` - -> [!WARNING] -> If you think that a subcomponent is likely to be reused in other components, -> ask the DS team to define it in the DS. -> -> It will be then moved in its own directory, following the DS guidelines. - -## DS Components MUST start with an HTML comment containing the implemented version - -In the form `v1`, `v2`, etc. - -> [!TIP] -> The DS team can use a minor version to indicate a change in the DS that does not affect the component style. -> -> It must not be added to the Vue component version. - -❌ Bad - -```vue - - -``` - -✅ Good - -```vue - - -``` - -## Subcomponents MUST NOT have a version number - -If a component from the DS is split into multiple subcomponents, only the main component will have a version number - -## Component tags MUST follow `template`, `script` then `style` order, separated with an empty line - -## Class names MUST use kebab-case - -## Component root element's class name MUST be named after the component name - -If no style is applied to the root element, the class name will be omitted - -❌ Bad - -```vue - - -``` - -```vue - - -``` - -✅ Good - -```vue - - -``` - -```vue - - -``` - -## Class names SHOULD be short and MUST be meaningful - -❌ Bad - -```vue - -``` - -✅ Good - -```vue - -``` - -## Component MUST use ` -``` - -✅ Good - -```vue - -``` - -> [!TIP] -> See also [Component variants guidelines](component-variants.md) -> to learn how to handle different component styles based on its props or states. - -## Optional slots container SHOULD use `v-if="$slots."` - -❌ Bad - -```vue - -``` - -✅ Good - -```vue - -``` - -## Component MUST use `defineSlots` when slots are used - -❌ Bad - -```vue - -``` - -```vue - -``` - -✅ Good - -```vue - - - -``` - -## Component SHOULD have a Story - -> [!TIP] -> For now, stories are stored in -> `@xen-orchestra/lite/src/stories` and can only be written for XO Lite and XO Core components. diff --git a/@xen-orchestra/web-core/docs/component-variants.md b/@xen-orchestra/web-core/docs/component-variants.md index caf2a3c3fbe..2f009dd6ab8 100644 --- a/@xen-orchestra/web-core/docs/component-variants.md +++ b/@xen-orchestra/web-core/docs/component-variants.md @@ -7,14 +7,27 @@ These variants are defined in the Design System and are reflected in the compone See also: [toVariants utility](../lib/utils/to-variants.util.md) to help you generate variant CSS classes for your components. +Component props that affect the variant MUST be specified in the component's `defineProps()`. They also MUST be **required**. + +> [!TIP] +> Example: +> +> The `accent` and `variant` props for a "Button" component are required and must be specified when using the component: +> +> ```vue +> +> ``` + ## Base class -The root element of a component will have a specific CSS class following the pattern `vts-`. +The root element of a component will have a specific CSS class following the pattern `ui-` if the component is part of the DS, or `vts-` if it's a _web-core_ component shared between XO 6 and XO Lite. > [!TIP] > Example: > -> The class for a "Button" component would be `vts-button`. +> The class for a DS "Button" component would be `ui-button`. ## Variant classes @@ -25,286 +38,237 @@ The pattern for these classes is `--` (or `` f > [!TIP] > Example: > -> If `color` prop is `success` and `size` prop is `medium` then the classes `color--success` and `size--medium` +> If `accent` prop is `success` and `size` prop is `medium` then the classes `accent--success` and `size--medium` > would be applied to the root element. ## Converting Design System props into Vue props The first step will be to convert the Design System's props into Vue props. -Some are easy to map, like `color` or `size`, which have a specific list of possible values. +Some are easy to map, like `accent` or `size`, which have a specific list of possible values. See [the list below](#ds-props-and-vue-props-list) for the full list of values. -But others are more tricky, like a `state` prop in the Design System having values like `default`, `hover`, `active`, or -`disabled`. +But others are more tricky, like an `interaction` prop in the Design System having values like `default`, `hover`, `active`, or `disabled`. -We can't simply create a `state` prop in Vue with these values (it wouldn't make sense for "hover" and "active" states). +We can't simply create an `interaction` prop in Vue with these values (it wouldn't make sense for "hover" and "active" states). So in this case: -- the "default" state would be represented as "no class applied." -- the "hover" and "active" states would be represented as `:hover` and `:active` pseudo-classes. -- the "disabled" state would be represented as a `disabled` `boolean` prop which would add a `disabled` class when `true`. +- the "default" interaction would be represented as "no class applied." +- the "hover" and "active" interactions would be represented as `:hover` and `:active` pseudo-classes. +- the "disabled" interaction would be represented as a `disabled` `boolean` prop which would add a `muted` or `disabled` class when `true` (depending on if the component can have an HTML `disabled` attribute or not). -## CSS variables +### DS props and Vue props list -Each CSS property that can be affected by a variant should have a corresponding CSS variable. +Here is the list of all possible values for each DS prop and Vue props: -The format for these variables is `----`. +- `accent`: + - `brand` + - `info` + - `neutral` + - `success` + - `warning` + - `danger` + - `muted` +- `size`: + - `small` + - `medium` + - `large` +- `variant`: + - `primary` + - `secondary` + - `tertiary` -> [!TIP] -> Example: -> -> For a `VtsButton` component, the CSS variables could be `--vts-button--background-color`, or `--vts-button--padding`. +## How to define variants' props -### CSS variables for child elements +First, we need to find in the DS, which values each prop can have. -If the CSS property to be changed is owned by a child element, the variable name should reflect that. +For example, imagine a "Button" component that can have three variants (`primary`, `secondary` and `tertiary`), four different accent colors (`info`, `success`, `warning` and `danger`), three sizes (`small`, `medium`, `large`) and can be `disabled`. -The format for these variables is `--__--`. +Since we use TypeScript, it's best practice to type the variants' props. -> [!TIP] -> Example: -> -> If we need to change `color` of a `.icon` inside our `VtsButton` component, the CSS variable will be -> `--vts-button__icon--color`. +### Component variants typing -## Identifying which DS props affect which CSS variables +We can start by creating the TypeScript types that these props will take. -The next step is to identify which CSS variables will be affected by each DS prop. +To keep things related, the typing can be done directly inside the `script` section of the component. -For example, we could imagine: +Example for a "Button" component: -- a `color` prop affecting the `background-color`, `color`, and `border-color` properties. -- a "state" affecting the `background-color` property. -- a `size` prop affecting the `padding`, `gap`, and `font-size` properties. -- a `level` prop affecting the `color` and `padding` properties. +```ts +type ButtonVariant = 'primary' | 'secondary' | 'tertiary' +type ButtonAccent = 'info' | 'success' | 'warning' | 'danger' +type ButtonSize = 'small' | 'medium' | 'large' +``` -## Grouping CSS variables declarations +There is no need to create a type for the `disabled` interaction, as it only a simple `boolean` and the type can be specified directly in `defineProps()`. -Once we know which CSS variables will be affected by each prop, we can group them accordingly. +### Define props -From the previous example, we can see that: +Then, we can define the props needed to handle the variants: -- `border-color` is affected by `color` only. -- `gap` and `font-size` are affected by `size` only. -- `background-color` is affected by both "state" and `color`. -- `color` is affected by both `color` and `level`. -- `padding` is affected by both `size` and `level` +```ts +// TS types here -So we could prepare our variables groups like this: +const props = defineProps<{ + variant: ButtonVariant + accent: ButtonAccent + size: ButtonSize + disabled?: boolean +}>() +``` -```postcss -/* -COLOR ---vts-button--border-color -*/ -.vts-button { - /* We'll define the border-color here */ -} +## How to define variants' CSS classes using the `toVariants()` helper -/* -SIZE ---vts-button--gap ---vts-button--font-size -*/ -.vts-button { - /* We'll define the gap and font-size here */ -} +Given the props we defined above, and to match the class names convention, we can define the classes with the `toVariants()` helper. -/* -COLOR + STATE ---vts-button--background-color -*/ -.vts-button { - /* We'll define the background-color here */ -} +A way to do it is as follows: -/* -COLOR + LEVEL ---vts-button--color -*/ -.vts-button { - /* We'll define the color here */ -} +```ts +const classNames = computed(() => [ + toVariants({ + variant: props.variant, + accent: props.accent, + size: props.size, + disabled: props.disabled, + }), +]) +``` + +Let's take an example where we want to use the "Button" component with this DS variant: -/* -SIZE + LEVEL ---vts-button--padding -*/ -.vts-button { - /* We'll define the padding here */ -} +``` +variant: primary +accent: info +size: medium +disabled: true ``` -## Filling the groups +By using the helper, the following classes would be generated, and be ready to use in the `template` section of the component: -Lastly, we can now fill in the CSS variables accordingly. +`variant--primary accent--info size--medium disabled` -Let's start with the `COLOR` group: +> [!TIP] +> Example: +> +> This code: +> +> `