@300codes/design-system 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/package.json +63 -0
- package/src/components/BaseIcon/BaseIcon.stories.ts +66 -0
- package/src/components/BaseIcon/BaseIcon.vue +96 -0
- package/src/components/BaseIcon/index.ts +2 -0
- package/src/components/BaseLabel/BaseLabel.stories.ts +114 -0
- package/src/components/BaseLabel/BaseLabel.vue +149 -0
- package/src/components/BaseLabel/index.ts +2 -0
- package/src/components/BaseTooltip/BaseTooltip.stories.ts +113 -0
- package/src/components/BaseTooltip/BaseTooltip.vue +123 -0
- package/src/components/BaseTooltip/index.ts +2 -0
- package/src/components/ButtonWithIcon/ButtonWithIcon.stories.ts +149 -0
- package/src/components/ButtonWithIcon/ButtonWithIcon.vue +77 -0
- package/src/components/ButtonWithIcon/index.ts +2 -0
- package/src/components/CheckboxInput/CheckboxInput.stories.ts +99 -0
- package/src/components/CheckboxInput/CheckboxInput.vue +176 -0
- package/src/components/CheckboxInput/index.ts +2 -0
- package/src/components/LabelInput/LabelInput.vue +111 -0
- package/src/components/LabelInput/index.ts +2 -0
- package/src/components/RadioInput/RadioInput.stories.ts +114 -0
- package/src/components/RadioInput/RadioInput.vue +174 -0
- package/src/components/RadioInput/index.ts +2 -0
- package/src/components/SearchInput/SearchInput.stories.ts +103 -0
- package/src/components/SearchInput/SearchInput.vue +83 -0
- package/src/components/SearchInput/index.ts +2 -0
- package/src/components/SelectInput/SelectInput.stories.ts +111 -0
- package/src/components/SelectInput/SelectInput.vue +497 -0
- package/src/components/SelectInput/index.ts +2 -0
- package/src/components/SelectInputField/SelectInputField.stories.ts +141 -0
- package/src/components/SelectInputField/SelectInputField.vue +64 -0
- package/src/components/SelectInputField/index.ts +2 -0
- package/src/components/SimpleButton/SimpleButton.stories.ts +143 -0
- package/src/components/SimpleButton/SimpleButton.vue +193 -0
- package/src/components/SimpleButton/index.ts +2 -0
- package/src/components/TabsList/TabsList.stories.ts +83 -0
- package/src/components/TabsList/TabsList.vue +156 -0
- package/src/components/TabsList/index.ts +2 -0
- package/src/components/TextInput/TextInput.stories.ts +125 -0
- package/src/components/TextInput/TextInput.vue +273 -0
- package/src/components/TextInput/components/InputIconButton.vue +54 -0
- package/src/components/TextInput/index.ts +2 -0
- package/src/components/TextInputField/TextInputField.stories.ts +133 -0
- package/src/components/TextInputField/TextInputField.vue +93 -0
- package/src/components/TextInputField/index.ts +2 -0
- package/src/components/index.ts +15 -0
- package/src/css/tokens.css +417 -0
- package/src/types/icon.ts +1 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
|
+
import type { ConcreteComponent } from 'vue';
|
|
4
|
+
import type { SelectInputFieldProps } from './SelectInputField.vue';
|
|
5
|
+
import SelectInputField from './SelectInputField.vue';
|
|
6
|
+
|
|
7
|
+
const OPTIONS = [
|
|
8
|
+
{ value: 'pl', label: 'Poland' },
|
|
9
|
+
{ value: 'de', label: 'Germany' },
|
|
10
|
+
{ value: 'fr', label: 'France' },
|
|
11
|
+
{ value: 'es', label: 'Spain' },
|
|
12
|
+
{ value: 'it', label: 'Italy' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const meta: Meta<SelectInputFieldProps> = {
|
|
16
|
+
title: 'Form/SelectInputField',
|
|
17
|
+
component: SelectInputField as unknown as ConcreteComponent<SelectInputFieldProps>,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
decorators: [
|
|
20
|
+
() => ({ template: '<div style="max-width: 25rem; width: 100%;"><story /></div>' }),
|
|
21
|
+
],
|
|
22
|
+
argTypes: {
|
|
23
|
+
size: { control: 'select', options: ['sm', 'md', 'lg'] },
|
|
24
|
+
invalid: { control: 'boolean' },
|
|
25
|
+
required: { control: 'boolean' },
|
|
26
|
+
disabled: { control: 'boolean' },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<SelectInputFieldProps>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
name: 'country',
|
|
36
|
+
label: 'Country',
|
|
37
|
+
options: OPTIONS,
|
|
38
|
+
placeholder: 'Select country',
|
|
39
|
+
size: 'md',
|
|
40
|
+
},
|
|
41
|
+
render: (args: SelectInputFieldProps) => ({
|
|
42
|
+
components: { SelectInputField },
|
|
43
|
+
setup() {
|
|
44
|
+
const value = ref('');
|
|
45
|
+
return { args, value };
|
|
46
|
+
},
|
|
47
|
+
template: '<SelectInputField v-bind="args" v-model="value" />',
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const WithMessage: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
name: 'country',
|
|
54
|
+
label: 'Country',
|
|
55
|
+
options: OPTIONS,
|
|
56
|
+
placeholder: 'Select country',
|
|
57
|
+
message: 'Please select a country',
|
|
58
|
+
invalid: true,
|
|
59
|
+
},
|
|
60
|
+
render: (args: SelectInputFieldProps) => ({
|
|
61
|
+
components: { SelectInputField },
|
|
62
|
+
setup() {
|
|
63
|
+
const value = ref('');
|
|
64
|
+
return { args, value };
|
|
65
|
+
},
|
|
66
|
+
template: '<SelectInputField v-bind="args" v-model="value" />',
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Valid: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
name: 'country',
|
|
73
|
+
label: 'Country',
|
|
74
|
+
options: OPTIONS,
|
|
75
|
+
placeholder: 'Select country',
|
|
76
|
+
invalid: false,
|
|
77
|
+
},
|
|
78
|
+
render: (args: SelectInputFieldProps) => ({
|
|
79
|
+
components: { SelectInputField },
|
|
80
|
+
setup() {
|
|
81
|
+
const value = ref('de');
|
|
82
|
+
return { args, value };
|
|
83
|
+
},
|
|
84
|
+
template: '<SelectInputField v-bind="args" v-model="value" />',
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const Required: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
name: 'country',
|
|
91
|
+
label: 'Country',
|
|
92
|
+
options: OPTIONS,
|
|
93
|
+
placeholder: 'Select country',
|
|
94
|
+
required: true,
|
|
95
|
+
},
|
|
96
|
+
render: (args: SelectInputFieldProps) => ({
|
|
97
|
+
components: { SelectInputField },
|
|
98
|
+
setup() {
|
|
99
|
+
const value = ref('');
|
|
100
|
+
return { args, value };
|
|
101
|
+
},
|
|
102
|
+
template: '<SelectInputField v-bind="args" v-model="value" />',
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const Disabled: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
name: 'country',
|
|
109
|
+
label: 'Country',
|
|
110
|
+
options: OPTIONS,
|
|
111
|
+
placeholder: 'Select country',
|
|
112
|
+
disabled: true,
|
|
113
|
+
},
|
|
114
|
+
render: (args: SelectInputFieldProps) => ({
|
|
115
|
+
components: { SelectInputField },
|
|
116
|
+
setup() {
|
|
117
|
+
const value = ref('pl');
|
|
118
|
+
return { args, value };
|
|
119
|
+
},
|
|
120
|
+
template: '<SelectInputField v-bind="args" v-model="value" />',
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const Sizes: Story = {
|
|
125
|
+
render: () => ({
|
|
126
|
+
components: { SelectInputField },
|
|
127
|
+
setup() {
|
|
128
|
+
const sm = ref('');
|
|
129
|
+
const md = ref('');
|
|
130
|
+
const lg = ref('');
|
|
131
|
+
return { sm, md, lg, OPTIONS };
|
|
132
|
+
},
|
|
133
|
+
template: `
|
|
134
|
+
<div class="flex flex-col gap-4 w-80">
|
|
135
|
+
<SelectInputField name="sm" label="Small" size="sm" placeholder="Select..." :options="OPTIONS" v-model="sm" />
|
|
136
|
+
<SelectInputField name="md" label="Medium" size="md" placeholder="Select..." :options="OPTIONS" v-model="md" />
|
|
137
|
+
<SelectInputField name="lg" label="Large" size="lg" placeholder="Select..." :options="OPTIONS" v-model="lg" />
|
|
138
|
+
</div>
|
|
139
|
+
`,
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import SelectInput from '../SelectInput/SelectInput.vue';
|
|
4
|
+
import LabelInput from '../LabelInput/LabelInput.vue';
|
|
5
|
+
import type { SelectOption } from '../SelectInput/SelectInput.vue';
|
|
6
|
+
|
|
7
|
+
export interface SelectInputFieldProps {
|
|
8
|
+
name: string;
|
|
9
|
+
label: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
options: readonly SelectOption[];
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
mobileTitle?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
invalid?: boolean | null;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
size?: 'sm' | 'md' | 'lg';
|
|
19
|
+
hasStatus?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<SelectInputFieldProps>(), {
|
|
23
|
+
id: undefined,
|
|
24
|
+
placeholder: '',
|
|
25
|
+
mobileTitle: undefined,
|
|
26
|
+
size: 'md',
|
|
27
|
+
invalid: null,
|
|
28
|
+
message: '',
|
|
29
|
+
disabled: false,
|
|
30
|
+
hasStatus: false,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const model = defineModel<string>({ required: true });
|
|
34
|
+
|
|
35
|
+
const selectInvalid = computed(() =>
|
|
36
|
+
typeof props.invalid === 'boolean' ? props.invalid : undefined,
|
|
37
|
+
);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<LabelInput
|
|
42
|
+
:name="id || name"
|
|
43
|
+
:label="label"
|
|
44
|
+
:message="message"
|
|
45
|
+
:required="required"
|
|
46
|
+
:invalid="invalid"
|
|
47
|
+
:disabled="disabled"
|
|
48
|
+
:size="size"
|
|
49
|
+
:has-status="hasStatus"
|
|
50
|
+
>
|
|
51
|
+
<SelectInput
|
|
52
|
+
:id="id"
|
|
53
|
+
v-model="model"
|
|
54
|
+
:name="name"
|
|
55
|
+
:options="options"
|
|
56
|
+
:placeholder="placeholder"
|
|
57
|
+
:mobile-title="mobileTitle"
|
|
58
|
+
:invalid="selectInvalid"
|
|
59
|
+
:required="required"
|
|
60
|
+
:disabled="disabled"
|
|
61
|
+
:size="size"
|
|
62
|
+
/>
|
|
63
|
+
</LabelInput>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import type { ConcreteComponent } from 'vue';
|
|
3
|
+
import type { SimpleButtonProps } from './SimpleButton.vue';
|
|
4
|
+
import SimpleButton from './SimpleButton.vue';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<SimpleButtonProps> = {
|
|
7
|
+
title: 'Components/SimpleButton',
|
|
8
|
+
component: SimpleButton as unknown as ConcreteComponent<SimpleButtonProps>,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
variant: {
|
|
12
|
+
control: 'select',
|
|
13
|
+
options: ['primary', 'secondary', 'tertiary'],
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['sm', 'md'],
|
|
18
|
+
},
|
|
19
|
+
type: {
|
|
20
|
+
control: 'select',
|
|
21
|
+
options: ['button', 'submit', 'reset'],
|
|
22
|
+
},
|
|
23
|
+
loading: { control: 'boolean' },
|
|
24
|
+
full: { control: 'boolean' },
|
|
25
|
+
disabled: { control: 'boolean' },
|
|
26
|
+
href: { control: 'text' },
|
|
27
|
+
target: { control: 'text' },
|
|
28
|
+
rel: { control: 'text' },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<SimpleButtonProps>;
|
|
34
|
+
|
|
35
|
+
export const Primary: Story = {
|
|
36
|
+
args: { variant: 'primary', size: 'md' },
|
|
37
|
+
render: (args: SimpleButtonProps) => ({
|
|
38
|
+
components: { SimpleButton },
|
|
39
|
+
setup() {
|
|
40
|
+
return { args };
|
|
41
|
+
},
|
|
42
|
+
template: '<SimpleButton v-bind="args">Button label</SimpleButton>',
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Secondary: Story = {
|
|
47
|
+
args: { variant: 'secondary', size: 'md' },
|
|
48
|
+
render: (args: SimpleButtonProps) => ({
|
|
49
|
+
components: { SimpleButton },
|
|
50
|
+
setup() {
|
|
51
|
+
return { args };
|
|
52
|
+
},
|
|
53
|
+
template: '<SimpleButton v-bind="args">Button label</SimpleButton>',
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Tertiary: Story = {
|
|
58
|
+
args: { variant: 'tertiary', size: 'md' },
|
|
59
|
+
render: (args: SimpleButtonProps) => ({
|
|
60
|
+
components: { SimpleButton },
|
|
61
|
+
setup() {
|
|
62
|
+
return { args };
|
|
63
|
+
},
|
|
64
|
+
template: '<SimpleButton v-bind="args">Button label</SimpleButton>',
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Loading: Story = {
|
|
69
|
+
args: { variant: 'primary', size: 'md', loading: true },
|
|
70
|
+
render: (args: SimpleButtonProps) => ({
|
|
71
|
+
components: { SimpleButton },
|
|
72
|
+
setup() {
|
|
73
|
+
return { args };
|
|
74
|
+
},
|
|
75
|
+
template: '<SimpleButton v-bind="args">Button label</SimpleButton>',
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const Disabled: Story = {
|
|
80
|
+
args: { variant: 'primary', size: 'md', disabled: true },
|
|
81
|
+
render: (args: SimpleButtonProps) => ({
|
|
82
|
+
components: { SimpleButton },
|
|
83
|
+
setup() {
|
|
84
|
+
return { args };
|
|
85
|
+
},
|
|
86
|
+
template: '<SimpleButton v-bind="args">Button label</SimpleButton>',
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const AsLink: Story = {
|
|
91
|
+
args: {
|
|
92
|
+
variant: 'primary',
|
|
93
|
+
size: 'md',
|
|
94
|
+
href: 'https://example.com',
|
|
95
|
+
target: '_blank',
|
|
96
|
+
rel: 'noopener noreferrer',
|
|
97
|
+
} satisfies SimpleButtonProps,
|
|
98
|
+
render: (args: SimpleButtonProps) => ({
|
|
99
|
+
components: { SimpleButton },
|
|
100
|
+
setup() {
|
|
101
|
+
return { args };
|
|
102
|
+
},
|
|
103
|
+
template: '<SimpleButton v-bind="args">Open link</SimpleButton>',
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const AsButtonSubmit: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
variant: 'primary',
|
|
110
|
+
size: 'md',
|
|
111
|
+
type: 'submit',
|
|
112
|
+
} satisfies SimpleButtonProps,
|
|
113
|
+
render: (args: SimpleButtonProps) => ({
|
|
114
|
+
components: { SimpleButton },
|
|
115
|
+
setup() {
|
|
116
|
+
return { args };
|
|
117
|
+
},
|
|
118
|
+
template: '<SimpleButton v-bind="args">Submit form</SimpleButton>',
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const FullWidth: Story = {
|
|
123
|
+
args: { variant: 'primary', size: 'md', full: true },
|
|
124
|
+
render: (args: SimpleButtonProps) => ({
|
|
125
|
+
components: { SimpleButton },
|
|
126
|
+
setup() {
|
|
127
|
+
return { args };
|
|
128
|
+
},
|
|
129
|
+
template: '<SimpleButton v-bind="args">Full width</SimpleButton>',
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const Sizes: Story = {
|
|
134
|
+
render: () => ({
|
|
135
|
+
components: { SimpleButton },
|
|
136
|
+
template: `
|
|
137
|
+
<div class="flex items-center gap-4">
|
|
138
|
+
<SimpleButton size="sm">Small</SimpleButton>
|
|
139
|
+
<SimpleButton size="md">Medium</SimpleButton>
|
|
140
|
+
</div>
|
|
141
|
+
`,
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'vue';
|
|
4
|
+
|
|
5
|
+
export interface SimpleButtonProps {
|
|
6
|
+
variant?: 'primary' | 'secondary' | 'tertiary';
|
|
7
|
+
size?: 'sm' | 'md';
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
full?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
href?: string;
|
|
12
|
+
type?: ButtonHTMLAttributes['type'];
|
|
13
|
+
name?: ButtonHTMLAttributes['name'];
|
|
14
|
+
value?: ButtonHTMLAttributes['value'];
|
|
15
|
+
target?: AnchorHTMLAttributes['target'];
|
|
16
|
+
rel?: AnchorHTMLAttributes['rel'];
|
|
17
|
+
download?: AnchorHTMLAttributes['download'];
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<SimpleButtonProps>(), {
|
|
22
|
+
variant: 'primary',
|
|
23
|
+
size: 'md',
|
|
24
|
+
loading: false,
|
|
25
|
+
full: false,
|
|
26
|
+
disabled: false,
|
|
27
|
+
href: undefined,
|
|
28
|
+
type: undefined,
|
|
29
|
+
name: undefined,
|
|
30
|
+
value: undefined,
|
|
31
|
+
target: undefined,
|
|
32
|
+
rel: undefined,
|
|
33
|
+
download: undefined,
|
|
34
|
+
ariaLabel: undefined,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const tag = computed(() => (props.href ? 'a' : 'button'));
|
|
38
|
+
|
|
39
|
+
const nativeAttrs = computed((): Record<string, unknown> => {
|
|
40
|
+
if (props.href) {
|
|
41
|
+
const isInactive = props.disabled || props.loading;
|
|
42
|
+
return {
|
|
43
|
+
href: isInactive ? undefined : props.href,
|
|
44
|
+
target: props.target,
|
|
45
|
+
rel: props.rel,
|
|
46
|
+
download: props.download,
|
|
47
|
+
'aria-disabled': isInactive ? 'true' : undefined,
|
|
48
|
+
'aria-label': props.ariaLabel,
|
|
49
|
+
tabindex: isInactive ? -1 : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type: props.type ?? 'button',
|
|
54
|
+
disabled: props.disabled || props.loading,
|
|
55
|
+
name: props.name,
|
|
56
|
+
value: props.value,
|
|
57
|
+
'aria-label': props.ariaLabel,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<component
|
|
64
|
+
:is="tag"
|
|
65
|
+
v-bind="nativeAttrs"
|
|
66
|
+
:aria-busy="props.loading ? 'true' : undefined"
|
|
67
|
+
:class="[
|
|
68
|
+
'simpleButton',
|
|
69
|
+
`simpleButton--${props.variant}`,
|
|
70
|
+
`simpleButton--${props.size}`,
|
|
71
|
+
'outline-offset-2 relative inline-flex items-center justify-center select-none cursor-pointer',
|
|
72
|
+
{
|
|
73
|
+
'w-full': props.full,
|
|
74
|
+
'simpleButton--loading': props.loading,
|
|
75
|
+
'simpleButton--disabled': props.disabled || props.loading,
|
|
76
|
+
},
|
|
77
|
+
]"
|
|
78
|
+
>
|
|
79
|
+
<slot />
|
|
80
|
+
</component>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<style scoped>
|
|
84
|
+
@reference "tailwindcss";
|
|
85
|
+
|
|
86
|
+
.simpleButton {
|
|
87
|
+
--_fg: var(--simpleButton-fg, #ffffff);
|
|
88
|
+
background-color: var(--simpleButton-bg, #0024d6);
|
|
89
|
+
color: var(--_fg);
|
|
90
|
+
border: var(--simpleButton-border-width, 1px) solid var(--simpleButton-border, #0024d6);
|
|
91
|
+
border-radius: var(--simpleButton-radius, 3.5rem);
|
|
92
|
+
padding: var(--simpleButton-py, 0.375rem) var(--simpleButton-px, 1.5rem);
|
|
93
|
+
min-height: var(--simpleButton-min-h, 3.5rem);
|
|
94
|
+
font-size: var(--simpleButton-font-size, 1rem);
|
|
95
|
+
font-weight: var(--simpleButton-font-weight, 600);
|
|
96
|
+
line-height: 1.2;
|
|
97
|
+
gap: var(--simpleButton-gap, 1rem);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.simpleButton:hover:not(.simpleButton--disabled) {
|
|
101
|
+
background-color: var(--simpleButton-bg-hover, #0e161b);
|
|
102
|
+
color: var(--simpleButton-fg-hover, var(--_fg));
|
|
103
|
+
border-color: var(--simpleButton-border-hover, #0e161b);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.simpleButton:focus-visible {
|
|
107
|
+
outline: 2px solid var(--simpleButton-ring, #0066cc);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.simpleButton--disabled {
|
|
111
|
+
--_fg: var(--simpleButton-disabled-fg, #89979f);
|
|
112
|
+
background-color: var(--simpleButton-disabled-bg, #f3f5f7);
|
|
113
|
+
color: var(--_fg);
|
|
114
|
+
border-color: var(--simpleButton-disabled-border, #f3f5f7);
|
|
115
|
+
|
|
116
|
+
@apply cursor-not-allowed pointer-events-none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ── secondary ── */
|
|
120
|
+
|
|
121
|
+
.simpleButton--secondary {
|
|
122
|
+
--_fg: var(--simpleButton-secondary-fg, #0e161b);
|
|
123
|
+
background-color: var(--simpleButton-secondary-bg, #f3f5f7);
|
|
124
|
+
color: var(--_fg);
|
|
125
|
+
border-color: var(--simpleButton-secondary-border, #f3f5f7);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.simpleButton--secondary:hover:not(.simpleButton--disabled) {
|
|
129
|
+
background-color: var(--simpleButton-secondary-bg-hover, #0e161b);
|
|
130
|
+
color: var(--simpleButton-secondary-fg-hover, #ffffff);
|
|
131
|
+
border-color: var(--simpleButton-secondary-border-hover, #0e161b);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.simpleButton--secondary:focus-visible {
|
|
135
|
+
outline: 2px solid var(--simpleButton-secondary-ring, #0066cc);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.simpleButton--secondary.simpleButton--disabled {
|
|
139
|
+
--_fg: var(--simpleButton-secondary-disabled-fg, #89979f);
|
|
140
|
+
background-color: var(--simpleButton-secondary-disabled-bg, #f3f5f7);
|
|
141
|
+
color: var(--_fg);
|
|
142
|
+
border-color: var(--simpleButton-secondary-disabled-border, #f3f5f7);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* ── tertiary ── */
|
|
146
|
+
|
|
147
|
+
.simpleButton--tertiary {
|
|
148
|
+
--_fg: var(--simpleButton-tertiary-fg, #0e161b);
|
|
149
|
+
background-color: var(--simpleButton-tertiary-bg, transparent);
|
|
150
|
+
color: var(--_fg);
|
|
151
|
+
border-color: var(--simpleButton-tertiary-border, transparent);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.simpleButton--tertiary:hover:not(.simpleButton--disabled) {
|
|
155
|
+
background-color: var(--simpleButton-tertiary-bg-hover, #f3f4f6);
|
|
156
|
+
color: var(--simpleButton-tertiary-fg-hover, var(--_fg));
|
|
157
|
+
border-color: var(--simpleButton-tertiary-border-hover, #f3f4f6);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.simpleButton--tertiary:focus-visible {
|
|
161
|
+
outline: 2px solid var(--simpleButton-tertiary-ring, #0066cc);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.simpleButton--tertiary.simpleButton--disabled {
|
|
165
|
+
--_fg: var(--simpleButton-tertiary-disabled-fg, #89979f);
|
|
166
|
+
background-color: var(--simpleButton-tertiary-disabled-bg, transparent);
|
|
167
|
+
color: var(--_fg);
|
|
168
|
+
border-color: var(--simpleButton-tertiary-disabled-border, transparent);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* ── sm ── */
|
|
172
|
+
|
|
173
|
+
.simpleButton--sm {
|
|
174
|
+
padding: var(--simpleButton-sm-py, 0.25rem) var(--simpleButton-sm-px, 1rem);
|
|
175
|
+
font-size: var(--simpleButton-sm-font-size, 0.875rem);
|
|
176
|
+
font-weight: var(--simpleButton-sm-font-weight, 600);
|
|
177
|
+
min-height: var(--simpleButton-sm-min-h, 2.5rem);
|
|
178
|
+
gap: var(--simpleButton-sm-gap, 0.625rem);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ── loading ── */
|
|
182
|
+
|
|
183
|
+
.simpleButton--loading {
|
|
184
|
+
color: transparent !important;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.simpleButton--loading::after {
|
|
188
|
+
content: '';
|
|
189
|
+
border-color: var(--_fg);
|
|
190
|
+
|
|
191
|
+
@apply absolute top-1/2 left-1/2 w-4 h-4 -mt-2 -ml-2 border-2 border-solid border-t-transparent border-b-transparent rounded-full animate-spin;
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
2
|
+
import type { ConcreteComponent } from 'vue';
|
|
3
|
+
import { ref } from 'vue';
|
|
4
|
+
import type { TabsListProps } from './TabsList.vue';
|
|
5
|
+
import TabsList from './TabsList.vue';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<TabsListProps> = {
|
|
8
|
+
title: 'Components/TabsList',
|
|
9
|
+
component: TabsList as unknown as ConcreteComponent<TabsListProps>,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {
|
|
12
|
+
size: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: ['md', 'lg'],
|
|
15
|
+
},
|
|
16
|
+
items: { control: 'object' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<TabsListProps>;
|
|
22
|
+
|
|
23
|
+
const defaultItems = [
|
|
24
|
+
{ label: 'Overview', value: 'overview' },
|
|
25
|
+
{ label: 'Analytics', value: 'analytics' },
|
|
26
|
+
{ label: 'Reports', value: 'reports' },
|
|
27
|
+
{ label: 'Settings', value: 'settings' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
size: 'md',
|
|
33
|
+
items: defaultItems,
|
|
34
|
+
},
|
|
35
|
+
render: (args: TabsListProps) => ({
|
|
36
|
+
components: { TabsList },
|
|
37
|
+
setup() {
|
|
38
|
+
const active = ref('overview');
|
|
39
|
+
return { args, active };
|
|
40
|
+
},
|
|
41
|
+
template: '<TabsList v-bind="args" v-model="active" />',
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Sizes: Story = {
|
|
46
|
+
parameters: {
|
|
47
|
+
controls: { disable: true },
|
|
48
|
+
},
|
|
49
|
+
render: () => ({
|
|
50
|
+
components: { TabsList },
|
|
51
|
+
setup() {
|
|
52
|
+
const activeMd = ref('overview');
|
|
53
|
+
const activeLg = ref('overview');
|
|
54
|
+
return { activeMd, activeLg, defaultItems };
|
|
55
|
+
},
|
|
56
|
+
template: `
|
|
57
|
+
<div class="flex flex-col gap-6">
|
|
58
|
+
<TabsList :items="defaultItems" v-model="activeMd" size="md" />
|
|
59
|
+
<TabsList :items="defaultItems" v-model="activeLg" size="lg" />
|
|
60
|
+
</div>
|
|
61
|
+
`,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const WithDisabled: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
size: 'md',
|
|
68
|
+
items: [
|
|
69
|
+
{ label: 'Overview', value: 'overview' },
|
|
70
|
+
{ label: 'Analytics', value: 'analytics' },
|
|
71
|
+
{ label: 'Reports', value: 'reports', disabled: true },
|
|
72
|
+
{ label: 'Settings', value: 'settings' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
render: (args: TabsListProps) => ({
|
|
76
|
+
components: { TabsList },
|
|
77
|
+
setup() {
|
|
78
|
+
const active = ref('overview');
|
|
79
|
+
return { args, active };
|
|
80
|
+
},
|
|
81
|
+
template: '<TabsList v-bind="args" v-model="active" />',
|
|
82
|
+
}),
|
|
83
|
+
};
|