mirror of
https://github.com/n8n-io/n8n.git
synced 2025-12-05 19:27:26 -06:00
fix(editor): Standardize CSS class naming conventions across new design system components (#22551)
This commit is contained in:
@@ -27,17 +27,17 @@ function onUpdate(value: boolean | 'indeterminate') {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as :class="$style.Checkbox" :data-disabled="disabled ? '' : undefined">
|
||||
<Primitive :as :class="$style.checkbox" :data-disabled="disabled ? '' : undefined">
|
||||
<CheckboxRoot
|
||||
:id="uuid"
|
||||
v-bind="{ ...rootProps, ...$attrs }"
|
||||
:model-value="computedValue"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
:class="$style.CheckboxRoot"
|
||||
:class="$style.checkboxRoot"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<CheckboxIndicator :class="$style.CheckboxIndicator">
|
||||
<CheckboxIndicator :class="$style.checkboxIndicator">
|
||||
<Icon v-if="indeterminate" icon="minus" size="small" />
|
||||
<Icon v-else icon="check" size="small" />
|
||||
</CheckboxIndicator>
|
||||
@@ -45,7 +45,7 @@ function onUpdate(value: boolean | 'indeterminate') {
|
||||
<Label
|
||||
v-if="label || !!slots.label"
|
||||
:for="uuid"
|
||||
:class="$style.Label"
|
||||
:class="$style.label"
|
||||
:data-disabled="disabled ? '' : undefined"
|
||||
>
|
||||
<slot name="label" :label="label">
|
||||
@@ -56,7 +56,7 @@ function onUpdate(value: boolean | 'indeterminate') {
|
||||
</template>
|
||||
|
||||
<style lang="css" module>
|
||||
.Checkbox {
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
@@ -67,7 +67,7 @@ function onUpdate(value: boolean | 'indeterminate') {
|
||||
}
|
||||
}
|
||||
|
||||
.CheckboxRoot {
|
||||
.checkboxRoot {
|
||||
background: transparent;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -97,13 +97,13 @@ function onUpdate(value: boolean | 'indeterminate') {
|
||||
}
|
||||
}
|
||||
|
||||
.CheckboxIndicator {
|
||||
.checkboxIndicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.Label {
|
||||
.label {
|
||||
padding-left: 15px;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, waitFor } from '@testing-library/vue';
|
||||
|
||||
import type { Input2Size } from './Input.types';
|
||||
import Input from './Input.vue';
|
||||
|
||||
const sizeCases: Array<[Input2Size, string]> = [
|
||||
['xlarge', 'xlarge'],
|
||||
['large', 'large'],
|
||||
['medium', 'medium'],
|
||||
['small', 'small'],
|
||||
['mini', 'mini'],
|
||||
];
|
||||
|
||||
describe('v2/components/Input', () => {
|
||||
describe('rendering', () => {
|
||||
it('should render with placeholder text', () => {
|
||||
@@ -36,13 +45,7 @@ describe('v2/components/Input', () => {
|
||||
});
|
||||
|
||||
describe('sizes', () => {
|
||||
test.each([
|
||||
['xlarge' as const, 'XLarge'],
|
||||
['large' as const, 'Large'],
|
||||
['medium' as const, 'Medium'],
|
||||
['small' as const, 'Small'],
|
||||
['mini' as const, 'Mini'],
|
||||
])('size %s should apply %s class', (size, expected) => {
|
||||
test.each(sizeCases)('size %s should apply %s class', (size, expected) => {
|
||||
const wrapper = render(Input, {
|
||||
props: {
|
||||
size,
|
||||
@@ -56,7 +59,7 @@ describe('v2/components/Input', () => {
|
||||
it('should default to large size', () => {
|
||||
const wrapper = render(Input);
|
||||
const container = wrapper.container.querySelector('div > div');
|
||||
expect(container?.className).toContain('Large');
|
||||
expect(container?.className).toContain('large');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -31,22 +31,22 @@ const inputRef = ref<HTMLInputElement | HTMLTextAreaElement | null>(null);
|
||||
|
||||
// Size class mapping
|
||||
const sizes: Record<Input2Size, string> = {
|
||||
xlarge: $style.XLarge,
|
||||
large: $style.Large,
|
||||
medium: $style.Medium,
|
||||
small: $style.Small,
|
||||
mini: $style.Mini,
|
||||
xlarge: $style.xlarge,
|
||||
large: $style.large,
|
||||
medium: $style.medium,
|
||||
small: $style.small,
|
||||
mini: $style.mini,
|
||||
};
|
||||
|
||||
const sizeClass = computed(() => sizes[props.size]);
|
||||
|
||||
// Classes for password type (PostHog privacy)
|
||||
const containerClasses = computed(() => [
|
||||
$style.InputContainer,
|
||||
$style.inputContainer,
|
||||
sizeClass.value,
|
||||
{
|
||||
[$style.Disabled]: props.disabled,
|
||||
[$style.Focused]: isFocused.value,
|
||||
[$style.disabled]: props.disabled,
|
||||
[$style.focused]: isFocused.value,
|
||||
'ph-no-capture': props.type === 'password',
|
||||
},
|
||||
]);
|
||||
@@ -238,7 +238,7 @@ defineExpose({ focus, blur, select });
|
||||
<template>
|
||||
<div :class="containerClasses">
|
||||
<!-- Prefix slot -->
|
||||
<span v-if="$slots.prefix" :class="$style.Prefix">
|
||||
<span v-if="$slots.prefix" :class="$style.prefix">
|
||||
<slot name="prefix" />
|
||||
</span>
|
||||
|
||||
@@ -248,7 +248,7 @@ defineExpose({ focus, blur, select });
|
||||
ref="inputRef"
|
||||
:type="type"
|
||||
:value="modelValue ?? ''"
|
||||
:class="$style.Input"
|
||||
:class="$style.input"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:maxlength="maxlength"
|
||||
@@ -264,7 +264,7 @@ defineExpose({ focus, blur, select });
|
||||
v-else
|
||||
ref="inputRef"
|
||||
:value="modelValue ?? ''"
|
||||
:class="[$style.Input, $style.Textarea]"
|
||||
:class="[$style.input, $style.textarea]"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:rows="autosize ? undefined : rows"
|
||||
@@ -278,7 +278,7 @@ defineExpose({ focus, blur, select });
|
||||
/>
|
||||
|
||||
<!-- Suffix slot -->
|
||||
<span v-if="$slots.suffix" :class="$style.Suffix">
|
||||
<span v-if="$slots.suffix" :class="$style.suffix">
|
||||
<slot name="suffix" />
|
||||
</span>
|
||||
|
||||
@@ -286,7 +286,7 @@ defineExpose({ focus, blur, select });
|
||||
<button
|
||||
v-if="showClearButton"
|
||||
type="button"
|
||||
:class="$style.ClearButton"
|
||||
:class="$style.clearButton"
|
||||
tabindex="-1"
|
||||
@click="onClear"
|
||||
>
|
||||
@@ -296,7 +296,7 @@ defineExpose({ focus, blur, select });
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.InputContainer {
|
||||
.inputContainer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
@@ -309,53 +309,53 @@ defineExpose({ focus, blur, select });
|
||||
gap: var(--spacing--3xs);
|
||||
}
|
||||
|
||||
.InputContainer:hover:not(.Disabled) {
|
||||
.inputContainer:hover:not(.disabled) {
|
||||
border-color: var(--color--foreground--shade-1);
|
||||
}
|
||||
|
||||
.Focused {
|
||||
.focused {
|
||||
border-color: var(--color--secondary);
|
||||
box-shadow: 0 0 0 2px var(--color--secondary--tint-2);
|
||||
}
|
||||
|
||||
.Disabled {
|
||||
.disabled {
|
||||
background-color: var(--color--background--light-3);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
.XLarge {
|
||||
.xlarge {
|
||||
min-height: 48px;
|
||||
padding: 0 var(--spacing--xs);
|
||||
font-size: var(--font-size--md);
|
||||
}
|
||||
|
||||
.Large {
|
||||
.large {
|
||||
min-height: 40px;
|
||||
padding: 0 var(--spacing--xs);
|
||||
font-size: var(--font-size--sm);
|
||||
}
|
||||
|
||||
.Medium {
|
||||
.medium {
|
||||
min-height: 36px;
|
||||
padding: 0 var(--spacing--2xs);
|
||||
font-size: var(--font-size--sm);
|
||||
}
|
||||
|
||||
.Small {
|
||||
.small {
|
||||
min-height: 28px;
|
||||
padding: 0 var(--spacing--2xs);
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.Mini {
|
||||
.mini {
|
||||
min-height: 22px;
|
||||
padding: 0 var(--spacing--3xs);
|
||||
font-size: var(--font-size--3xs);
|
||||
}
|
||||
|
||||
.Input {
|
||||
.input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
@@ -367,30 +367,30 @@ defineExpose({ focus, blur, select });
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.Input::placeholder {
|
||||
.input::placeholder {
|
||||
color: var(--color--text--tint-1);
|
||||
}
|
||||
|
||||
.Input:disabled {
|
||||
.input:disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--color--text--tint-1);
|
||||
}
|
||||
|
||||
.Textarea {
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
line-height: var(--line-height--md);
|
||||
padding: var(--spacing--2xs) 0;
|
||||
}
|
||||
|
||||
.Prefix,
|
||||
.Suffix {
|
||||
.prefix,
|
||||
.suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--color--text--tint-1);
|
||||
}
|
||||
|
||||
.ClearButton {
|
||||
.clearButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -404,11 +404,11 @@ defineExpose({ focus, blur, select });
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.ClearButton:hover {
|
||||
.clearButton:hover {
|
||||
color: var(--color--text--shade-1);
|
||||
}
|
||||
|
||||
.ClearButton:focus {
|
||||
.clearButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, waitFor } from '@testing-library/vue';
|
||||
|
||||
import type { PaginationSizes, PaginationVariants } from './Pagination.types';
|
||||
import Pagination from './Pagination.vue';
|
||||
|
||||
const sizeCases: Array<[PaginationSizes | undefined, string]> = [
|
||||
[undefined, 'small'],
|
||||
['small', 'small'],
|
||||
['medium', 'medium'],
|
||||
];
|
||||
|
||||
const variantCases: Array<[PaginationVariants | undefined, string]> = [
|
||||
[undefined, 'default'],
|
||||
['default', 'default'],
|
||||
['ghost', 'ghost'],
|
||||
];
|
||||
|
||||
describe('v2/components/Pagination', () => {
|
||||
describe('rendering', () => {
|
||||
it('should render with default props', () => {
|
||||
@@ -58,11 +71,7 @@ describe('v2/components/Pagination', () => {
|
||||
});
|
||||
|
||||
describe('sizes', () => {
|
||||
test.each([
|
||||
[undefined, 'Small'],
|
||||
['small' as const, 'Small'],
|
||||
['medium' as const, 'Medium'],
|
||||
])('size %s should apply %s class', (size, expected) => {
|
||||
test.each(sizeCases)('size %s should apply %s class', (size, expected) => {
|
||||
const wrapper = render(Pagination, {
|
||||
props: {
|
||||
total: 100,
|
||||
@@ -75,11 +84,7 @@ describe('v2/components/Pagination', () => {
|
||||
});
|
||||
|
||||
describe('variants', () => {
|
||||
test.each([
|
||||
[undefined, 'Default'],
|
||||
['default' as const, 'Default'],
|
||||
['ghost' as const, 'Ghost'],
|
||||
])('variant %s should apply %s class', (variant, expected) => {
|
||||
test.each(variantCases)('variant %s should apply %s class', (variant, expected) => {
|
||||
const wrapper = render(Pagination, {
|
||||
props: {
|
||||
total: 100,
|
||||
@@ -98,7 +103,7 @@ describe('v2/components/Pagination', () => {
|
||||
},
|
||||
});
|
||||
const container = wrapper.container.firstChild as HTMLElement;
|
||||
expect(container?.className).toContain('Background');
|
||||
expect(container?.className).toContain('background');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -161,19 +161,19 @@ const handlePageSizeUpdate = (newSize: number | string) => {
|
||||
|
||||
// Styles
|
||||
const variants: Record<PaginationVariants, string> = {
|
||||
default: $style.Default,
|
||||
ghost: $style.Ghost,
|
||||
default: $style.default,
|
||||
ghost: $style.ghost,
|
||||
};
|
||||
const variant = computed(() => variants[props.variant]);
|
||||
|
||||
const sizes: Record<PaginationSizes, string> = {
|
||||
small: $style.Small,
|
||||
medium: $style.Medium,
|
||||
small: $style.small,
|
||||
medium: $style.medium,
|
||||
};
|
||||
const size = computed(() => sizes[props.size]);
|
||||
|
||||
// Background class
|
||||
const backgroundClass = computed(() => (props.background ? $style.Background : ''));
|
||||
const backgroundClass = computed(() => (props.background ? $style.background : ''));
|
||||
|
||||
// Page size selector items
|
||||
const pageSizeItems = computed(() =>
|
||||
@@ -200,13 +200,13 @@ const handleJumperSubmit = () => {
|
||||
<template>
|
||||
<div
|
||||
v-if="!shouldHide"
|
||||
:class="['n8n-pagination', $style.PaginationContainer, variant, size, backgroundClass]"
|
||||
:class="['n8n-pagination', $style.paginationContainer, variant, size, backgroundClass]"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<!-- Render layout parts in order specified by layout prop -->
|
||||
<template v-for="part in deduplicatedLayoutParts" :key="part">
|
||||
<!-- Total count -->
|
||||
<div v-if="part === 'total'" :class="$style.Total">
|
||||
<div v-if="part === 'total'" :class="$style.total">
|
||||
{{ totalText }}
|
||||
</div>
|
||||
|
||||
@@ -218,7 +218,7 @@ const handleJumperSubmit = () => {
|
||||
:size="props.size === 'small' ? 'xsmall' : 'small'"
|
||||
:variant="props.variant === 'ghost' ? 'ghost' : 'default'"
|
||||
:disabled="disabled"
|
||||
:class="$style.PageSizeSelect"
|
||||
:class="$style.pageSizeSelect"
|
||||
@update:model-value="handlePageSizeUpdate"
|
||||
/>
|
||||
|
||||
@@ -233,9 +233,9 @@ const handleJumperSubmit = () => {
|
||||
:show-edges="showEdges"
|
||||
@update:page="handlePageUpdate"
|
||||
>
|
||||
<PaginationList v-slot="{ items }" :class="$style.PaginationList">
|
||||
<PaginationList v-slot="{ items }" :class="$style.paginationList">
|
||||
<!-- Previous button -->
|
||||
<PaginationPrev v-if="showPrev" v-slot="slotProps" :class="$style.PaginationButton">
|
||||
<PaginationPrev v-if="showPrev" v-slot="slotProps" :class="$style.paginationButton">
|
||||
<slot name="prev" v-bind="slotProps">
|
||||
<span v-if="prevText">{{ prevText }}</span>
|
||||
<Icon v-else :icon="prevIcon" />
|
||||
@@ -252,18 +252,18 @@ const handleJumperSubmit = () => {
|
||||
<PaginationEllipsis
|
||||
v-if="item.type === 'ellipsis'"
|
||||
:index="index"
|
||||
:class="[$style.PaginationEllipsis, $style.PaginationButton]"
|
||||
:class="[$style.paginationEllipsis, $style.paginationButton]"
|
||||
>
|
||||
<span aria-hidden="true">…</span>
|
||||
</PaginationEllipsis>
|
||||
<PaginationListItem v-else :value="item.value" :class="$style.PaginationItem">
|
||||
<PaginationListItem v-else :value="item.value" :class="$style.paginationItem">
|
||||
{{ item.value }}
|
||||
</PaginationListItem>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Next button -->
|
||||
<PaginationNext v-if="showNext" v-slot="slotProps" :class="$style.PaginationButton">
|
||||
<PaginationNext v-if="showNext" v-slot="slotProps" :class="$style.paginationButton">
|
||||
<slot name="next" v-bind="slotProps">
|
||||
<span v-if="nextText">{{ nextText }}</span>
|
||||
<Icon v-else :icon="nextIcon" />
|
||||
@@ -273,14 +273,14 @@ const handleJumperSubmit = () => {
|
||||
</PaginationRoot>
|
||||
|
||||
<!-- Page jumper -->
|
||||
<div v-else-if="part === 'jumper'" :class="$style.Jumper">
|
||||
<span :class="$style.JumperText">Go to</span>
|
||||
<div v-else-if="part === 'jumper'" :class="$style.jumper">
|
||||
<span :class="$style.jumperText">Go to</span>
|
||||
<input
|
||||
v-model="jumperValue"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="pageCount"
|
||||
:class="$style.JumperInput"
|
||||
:class="$style.jumperInput"
|
||||
:disabled="disabled"
|
||||
@keyup.enter="handleJumperSubmit"
|
||||
/>
|
||||
@@ -290,7 +290,7 @@ const handleJumperSubmit = () => {
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.PaginationContainer {
|
||||
.paginationContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing--xs);
|
||||
@@ -298,43 +298,43 @@ const handleJumperSubmit = () => {
|
||||
color: var(--color--text--shade-1);
|
||||
}
|
||||
|
||||
.Default {
|
||||
.default {
|
||||
/* Default styling */
|
||||
}
|
||||
|
||||
.Ghost {
|
||||
.ghost {
|
||||
/* Ghost variant */
|
||||
}
|
||||
|
||||
.Small {
|
||||
.small {
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.Medium {
|
||||
.medium {
|
||||
font-size: var(--font-size--xs);
|
||||
}
|
||||
|
||||
.Background {
|
||||
.background {
|
||||
/* Applied when background prop is true */
|
||||
}
|
||||
|
||||
.Total {
|
||||
.total {
|
||||
color: var(--color--text--tint-1);
|
||||
margin-right: var(--spacing--2xs);
|
||||
}
|
||||
|
||||
.PageSizeSelect {
|
||||
.pageSizeSelect {
|
||||
margin-right: var(--spacing--2xs);
|
||||
}
|
||||
|
||||
.PaginationList {
|
||||
.paginationList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing--4xs);
|
||||
}
|
||||
|
||||
/* Prev/Next buttons - minimal style like Element+ */
|
||||
.PaginationButton {
|
||||
.paginationButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -360,7 +360,7 @@ const handleJumperSubmit = () => {
|
||||
}
|
||||
|
||||
/* Page number buttons - minimal style like Element+ */
|
||||
.PaginationItem {
|
||||
.paginationItem {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -394,7 +394,7 @@ const handleJumperSubmit = () => {
|
||||
}
|
||||
|
||||
/* Background variant - filled style */
|
||||
.Background .PaginationItem {
|
||||
.background .paginationItem {
|
||||
background-color: var(--color--background--light-2);
|
||||
border-color: var(--color--foreground);
|
||||
|
||||
@@ -406,7 +406,7 @@ const handleJumperSubmit = () => {
|
||||
}
|
||||
|
||||
/* Ellipsis - minimal style */
|
||||
.PaginationEllipsis {
|
||||
.paginationEllipsis {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -426,18 +426,18 @@ const handleJumperSubmit = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.Jumper {
|
||||
.jumper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing--3xs);
|
||||
margin-left: var(--spacing--2xs);
|
||||
}
|
||||
|
||||
.JumperText {
|
||||
.jumperText {
|
||||
color: var(--color--text--tint-1);
|
||||
}
|
||||
|
||||
.JumperInput {
|
||||
.jumperInput {
|
||||
width: 50px;
|
||||
height: var(--spacing--lg);
|
||||
padding: 0 var(--spacing--3xs);
|
||||
@@ -472,9 +472,9 @@ const handleJumperSubmit = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.Medium .PaginationButton,
|
||||
.Medium .PaginationItem,
|
||||
.Medium .JumperInput {
|
||||
.medium .paginationButton,
|
||||
.medium .paginationItem,
|
||||
.medium .jumperInput {
|
||||
height: 36px;
|
||||
min-width: 36px;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, waitFor, within } from '@testing-library/vue';
|
||||
|
||||
import type { SelectItem } from './Select.types';
|
||||
import type { SelectItem, SelectSizes, SelectVariants } from './Select.types';
|
||||
import Select from './Select.vue';
|
||||
|
||||
const sizeCases: Array<[SelectSizes | undefined, string]> = [
|
||||
[undefined, 'small'],
|
||||
['xsmall', 'xsmall'],
|
||||
['small', 'small'],
|
||||
['medium', 'medium'],
|
||||
];
|
||||
|
||||
const variantCases: Array<[SelectVariants | undefined, string]> = [
|
||||
[undefined, 'default'],
|
||||
['default', 'default'],
|
||||
['ghost', 'ghost'],
|
||||
];
|
||||
|
||||
async function getPopoverContainer(trigger: Element | null) {
|
||||
const popoverId = trigger?.getAttribute('aria-controls');
|
||||
|
||||
@@ -52,12 +65,7 @@ describe('v2/components/Select', () => {
|
||||
});
|
||||
|
||||
describe('sizes', () => {
|
||||
test.each([
|
||||
[undefined, 'Small'],
|
||||
['xsmall' as const, 'XSmall'],
|
||||
['small' as const, 'Small'],
|
||||
['medium' as const, 'Medium'],
|
||||
])('variant %s should apply %s class', (size, expected) => {
|
||||
test.each(sizeCases)('size %s should apply %s class', (size, expected) => {
|
||||
const wrapper = render(Select, {
|
||||
props: {
|
||||
items: ['Option 1'],
|
||||
@@ -70,11 +78,7 @@ describe('v2/components/Select', () => {
|
||||
});
|
||||
|
||||
describe('variants', () => {
|
||||
test.each([
|
||||
[undefined, 'Default'],
|
||||
['default' as const, 'Default'],
|
||||
['ghost' as const, 'Ghost'],
|
||||
])('variant %s should apply %s class', (variant, expected) => {
|
||||
test.each(variantCases)('variant %s should apply %s class', (variant, expected) => {
|
||||
const wrapper = render(Select, {
|
||||
props: {
|
||||
items: ['Option 1'],
|
||||
|
||||
@@ -74,16 +74,16 @@ defineExpose({
|
||||
});
|
||||
|
||||
const variants: Record<SelectVariants, string> = {
|
||||
default: $style.Default,
|
||||
ghost: $style.Ghost,
|
||||
default: $style.default,
|
||||
ghost: $style.ghost,
|
||||
};
|
||||
|
||||
const variant = computed(() => variants[props.variant]);
|
||||
|
||||
const sizes: Record<SelectSizes, string> = {
|
||||
xsmall: $style.XSmall,
|
||||
small: $style.Small,
|
||||
medium: $style.Medium,
|
||||
xsmall: $style.xsmall,
|
||||
small: $style.small,
|
||||
medium: $style.medium,
|
||||
};
|
||||
const size = computed(() => sizes[props.size]);
|
||||
|
||||
@@ -96,9 +96,9 @@ const strokeWidths = {
|
||||
const iconStrokeWidth = computed(() => strokeWidths[props.size]);
|
||||
|
||||
const labelSizes: Record<SelectSizes, string> = {
|
||||
xsmall: $style.SelectLabelXSmall,
|
||||
small: $style.SelectLabelSmall,
|
||||
medium: $style.SelectLabelMedium,
|
||||
xsmall: $style.selectLabelXsmall,
|
||||
small: $style.selectLabelSmall,
|
||||
medium: $style.selectLabelMedium,
|
||||
};
|
||||
const labelSize = computed(() => labelSizes[props.size]);
|
||||
|
||||
@@ -110,13 +110,13 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
...item,
|
||||
value: get(item, props.valueKey?.toString() ?? 'value'),
|
||||
label: get(item, props.labelKey?.toString() ?? 'label'),
|
||||
class: [$style.SelectItem, item.class, size.value],
|
||||
class: [$style.selectItem, item.class, size.value],
|
||||
strokeWidth: iconStrokeWidth.value,
|
||||
}
|
||||
: {
|
||||
value: item,
|
||||
label: String(item),
|
||||
class: [$style.SelectItem, size.value],
|
||||
class: [$style.selectItem, size.value],
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -136,32 +136,32 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
:id="id"
|
||||
ref="trigger"
|
||||
v-bind="$attrs"
|
||||
:class="[$style.SelectTrigger, variant, size]"
|
||||
:class="[$style.selectTrigger, variant, size]"
|
||||
:aria-label="$attrs['aria-label'] ?? placeholder"
|
||||
>
|
||||
<Icon v-if="icon" :icon="icon" :class="$style.SelectedIcon" :stroke-width="iconStrokeWidth" />
|
||||
<RSelectValue :placeholder="placeholder" :class="$style.SelectValue">
|
||||
<Icon v-if="icon" :icon="icon" :class="$style.selectedIcon" :stroke-width="iconStrokeWidth" />
|
||||
<RSelectValue :placeholder="placeholder" :class="$style.selectValue">
|
||||
<slot :model-value="modelValue" :open="open" />
|
||||
</RSelectValue>
|
||||
<Icon icon="chevron-down" :class="$style.TrailingIcon" />
|
||||
<Icon icon="chevron-down" :class="$style.trailingIcon" />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectPortal>
|
||||
<SelectContent :class="$style.SelectContent">
|
||||
<SelectScrollUpButton :class="$style.SelectScrollButton">
|
||||
<SelectContent :class="$style.selectContent">
|
||||
<SelectScrollUpButton :class="$style.selectScrollButton">
|
||||
<Icon icon="chevron-up" />
|
||||
</SelectScrollUpButton>
|
||||
|
||||
<SelectViewport :class="$style.SelectViewport">
|
||||
<SelectViewport :class="$style.selectViewport">
|
||||
<SelectGroup>
|
||||
<template v-for="(item, index) in groups" :key="`group-${index}`">
|
||||
<SelectLabel v-if="item.type === 'label'" :class="[$style.SelectLabel, labelSize]">
|
||||
<SelectLabel v-if="item.type === 'label'" :class="[$style.selectLabel, labelSize]">
|
||||
{{ item.label }}
|
||||
</SelectLabel>
|
||||
|
||||
<SelectSeparator
|
||||
v-else-if="item.type === 'separator'"
|
||||
:class="$style.SelectSeparator"
|
||||
:class="$style.selectSeparator"
|
||||
role="separator"
|
||||
/>
|
||||
|
||||
@@ -182,7 +182,7 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
</SelectGroup>
|
||||
</SelectViewport>
|
||||
|
||||
<SelectScrollDownButton :class="$style.SelectScrollButton">
|
||||
<SelectScrollDownButton :class="$style.selectScrollButton">
|
||||
<Icon icon="chevron-down" />
|
||||
</SelectScrollDownButton>
|
||||
</SelectContent>
|
||||
@@ -191,7 +191,7 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.SelectTrigger {
|
||||
.selectTrigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -228,44 +228,44 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.Default {
|
||||
.default {
|
||||
border: var(--border);
|
||||
}
|
||||
|
||||
.Ghost {
|
||||
.ghost {
|
||||
/** nothing to see here */
|
||||
}
|
||||
|
||||
.XSmall {
|
||||
.xsmall {
|
||||
min-height: var(--spacing--lg);
|
||||
padding: 0 var(--spacing--2xs);
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.Small {
|
||||
.small {
|
||||
min-height: 28px;
|
||||
padding: 0 var(--spacing--xs);
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.Medium {
|
||||
.medium {
|
||||
min-height: 36px;
|
||||
padding: 0 var(--spacing--xs);
|
||||
font-size: var(--font-size--sm);
|
||||
line-height: var(--line-height--sm);
|
||||
}
|
||||
|
||||
.SelectedIcon {
|
||||
.selectedIcon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.TrailingIcon {
|
||||
.trailingIcon {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
color: var(--color--text--shade-1);
|
||||
}
|
||||
|
||||
.SelectContent {
|
||||
.selectContent {
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius);
|
||||
border: var(--border);
|
||||
@@ -278,17 +278,17 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.SelectViewport {
|
||||
.selectViewport {
|
||||
padding: var(--spacing--4xs);
|
||||
}
|
||||
|
||||
.SelectValue {
|
||||
.selectValue {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.SelectItem {
|
||||
.selectItem {
|
||||
font-size: var(--font-size--xs);
|
||||
line-height: 1;
|
||||
border-radius: var(--radius);
|
||||
@@ -315,30 +315,30 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.SelectLabel {
|
||||
.selectLabel {
|
||||
padding: var(--spacing--3xs) var(--spacing--2xs) var(--spacing--4xs);
|
||||
color: var(--color--text--tint-1);
|
||||
}
|
||||
|
||||
.SelectLabelMedium {
|
||||
.selectLabelMedium {
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.SelectLabelSmall {
|
||||
.selectLabelSmall {
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.SelectLabelXSmall {
|
||||
.selectLabelXsmall {
|
||||
font-size: var(--font-size--2xs);
|
||||
}
|
||||
|
||||
.SelectSeparator {
|
||||
.selectSeparator {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: var(--spacing--3xs);
|
||||
}
|
||||
|
||||
.SelectItemIndicator {
|
||||
.selectItemIndicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 25px;
|
||||
@@ -347,7 +347,7 @@ const groups = computed<SelectItemProps[]>(() => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.SelectScrollButton {
|
||||
.selectScrollButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -15,11 +15,11 @@ function isAcceptable(value?: SelectValue) {
|
||||
}
|
||||
|
||||
const leadingProps = computed(() => ({
|
||||
class: $style.ItemLeading,
|
||||
class: $style.itemLeading,
|
||||
strokeWidth: props.strokeWidth,
|
||||
}));
|
||||
const trailingProps = computed(() => ({
|
||||
class: $style.ItemTrailing,
|
||||
class: $style.itemTrailing,
|
||||
strokeWidth: props.strokeWidth,
|
||||
}));
|
||||
</script>
|
||||
@@ -35,7 +35,7 @@ const trailingProps = computed(() => ({
|
||||
<Icon v-if="props.icon" :icon="props.icon" v-bind="leadingProps" />
|
||||
</slot>
|
||||
|
||||
<SelectItemText :class="$style.ItemText">
|
||||
<SelectItemText :class="$style.itemText">
|
||||
<slot name="item-label" :item="props">
|
||||
{{ props.label }}
|
||||
</slot>
|
||||
@@ -43,22 +43,22 @@ const trailingProps = computed(() => ({
|
||||
|
||||
<slot name="item-trailing" :item="props" :ui="trailingProps" />
|
||||
<SelectItemIndicator as-child>
|
||||
<Icon icon="check" :class="$style.ItemIndicator" />
|
||||
<Icon icon="check" :class="$style.itemIndicator" />
|
||||
</SelectItemIndicator>
|
||||
</SelectItem>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.ItemLeading {
|
||||
.itemLeading {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ItemText {
|
||||
.itemText {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ItemIndicator,
|
||||
.ItemTrailing {
|
||||
.itemIndicator,
|
||||
.itemTrailing {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user