vueless 1.0.2-beta.11 → 1.0.2-beta.12

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.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M319-249.52h322v-62.63H319v62.63Zm0-170h322v-62.63H319v62.63Zm-96.85 345.5q-27.6 0-47.86-20.27-20.27-20.26-20.27-47.86v-675.7q0-27.7 20.27-48.03 20.26-20.34 47.86-20.34h361.48l222.59 222.59v521.48q0 27.6-20.34 47.86-20.33 20.27-48.03 20.27h-515.7Zm326.7-557.83v-186h-326.7v675.7h515.7v-489.7h-189Zm-326.7-186v186-186 675.7-675.7Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="m624.37-286.04 49.06-48.59-157.8-158.86v-198.55h-65.26v224.89l174 181.11ZM480.03-74.02q-83.46 0-157.51-31.95-74.05-31.94-129.32-87.21-55.28-55.26-87.23-129.3-31.95-74.03-31.95-157.49 0-83.46 32-157.54 32-74.07 87.2-129.27 55.2-55.2 129.25-87.32 74.05-32.12 157.53-32.12t157.53 32.12q74.05 32.12 129.25 87.32 55.2 55.2 87.32 129.25 32.12 74.05 32.12 157.53T854.1-322.47q-32.12 74.05-87.32 129.25-55.2 55.2-129.24 87.2t-157.51 32ZM480-480Zm-.12 337.85q139.16 0 238.57-99.17 99.4-99.16 99.4-238.56t-99.37-238.69Q619.11-817.85 480-817.85q-139.28 0-238.57 99.26-99.28 99.25-99.28 238.59 0 139.52 99.28 238.68 99.28 99.17 238.45 99.17Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M142.63-235.46q-27.6 0-47.86-20.33-20.27-20.34-20.27-48.04v-352.34q0-26.7 20.27-47.54 20.26-20.83 47.86-20.83h674.74q27.6 0 47.86 20.83 20.27 20.84 20.27 47.54v352.34q0 27.7-20.27 48.04-20.26 20.33-47.86 20.33H142.63Zm0-68.37h674.74v-352.34H690V-480h-60v-176.17H510V-480h-60v-176.17H330V-480h-60v-176.17H142.63v352.34ZM270-480h60-60Zm180 0h60-60Zm180 0h60-60Zm-150 0Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.0.2-beta.11",
3
+ "version": "1.0.2-beta.12",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
6
6
  "keywords": [
@@ -1,25 +1,15 @@
1
1
  export default /*tw*/ {
2
- wrapper: {
3
- base: "relative inline-block",
4
- variants: {
5
- disabled: {
6
- true: "cursor-not-allowed",
7
- },
8
- },
9
- },
2
+ wrapper: "relative inline-block h-max",
10
3
  dropdownBadge: {
11
4
  base: "{UBadge}",
12
5
  variants: {
13
- opened: {
14
- true: "group",
15
- },
16
6
  disabled: {
17
7
  true: "opacity-(--vl-disabled-opacity) pointer-events-none",
18
8
  },
19
9
  },
20
10
  },
21
11
  toggleIcon: {
22
- base: "{UIcon} transition duration-300 group-[*]:rotate-180 -mr-0.5",
12
+ base: "{UIcon} transition duration-300 -mr-0.5",
23
13
  defaults: {
24
14
  size: {
25
15
  sm: "2xs",
@@ -27,6 +17,7 @@ export default /*tw*/ {
27
17
  lg: "xs",
28
18
  },
29
19
  },
20
+ compoundVariants: [{ opened: true, class: "rotate-180" }],
30
21
  },
31
22
  listbox: {
32
23
  base: "{UListbox} w-fit",
@@ -1,15 +1,8 @@
1
1
  export default /*tw*/ {
2
- wrapper: "relative inline-block",
3
- dropdownButton: {
4
- base: "{UButton} justify-between",
5
- variants: {
6
- opened: {
7
- true: "group",
8
- },
9
- },
10
- },
2
+ wrapper: "relative inline-block h-max",
3
+ dropdownButton: "{UButton} justify-between",
11
4
  toggleIcon: {
12
- base: "{UIcon} transition duration-300 group-[*]:rotate-180 -mr-1",
5
+ base: "{UIcon} transition duration-300 -mr-1",
13
6
  defaults: {
14
7
  size: {
15
8
  "2xs": "2xs",
@@ -20,6 +13,7 @@ export default /*tw*/ {
20
13
  xl: "sm",
21
14
  },
22
15
  },
16
+ compoundVariants: [{ opened: true, class: "rotate-180" }],
23
17
  },
24
18
  listbox: {
25
19
  base: "{UListbox} w-fit",
@@ -1,18 +1,11 @@
1
1
  export default /*tw*/ {
2
- wrapper: {
3
- base: `
4
- inline-flex gap-0.5 relative items-center justify-between rounded
5
- focus-visible:outline focus-visible:outline-medium focus-visible:outline-offset-4 focus-visible:outline-{color}
6
- `,
7
- variants: {
8
- opened: {
9
- true: "group",
10
- },
11
- },
12
- },
2
+ wrapper: `
3
+ inline-flex gap-0.5 relative items-center justify-between rounded h-max
4
+ focus-visible:outline focus-visible:outline-medium focus-visible:outline-offset-4 focus-visible:outline-{color}
5
+ `,
13
6
  dropdownLink: "{ULink} focus-visible:outline-hidden",
14
7
  toggleIcon: {
15
- base: "{UIcon} block transition duration-300 group-[*]:rotate-180",
8
+ base: "{UIcon} block transition duration-300",
16
9
  defaults: {
17
10
  size: {
18
11
  sm: "2xs",
@@ -20,6 +13,7 @@ export default /*tw*/ {
20
13
  lg: "sm",
21
14
  },
22
15
  },
16
+ compoundVariants: [{ opened: true, class: "rotate-180" }],
23
17
  },
24
18
  listbox: {
25
19
  base: "{UListbox} w-fit",
@@ -162,8 +162,12 @@ export const DefaultSlot = DefaultTemplate.bind({});
162
162
  DefaultSlot.args = {
163
163
  toggleIcon: false,
164
164
  slotTemplate: `
165
- <template #default>
166
- <UAvatar rounded="full" src="https://avatar.iran.liara.run/public" />
165
+ <template #default="{ opened }">
166
+ <UAvatar
167
+ rounded="full"
168
+ src="https://avatar.iran.liara.run/public"
169
+ :class="{ 'outline-medium outline-primary': opened }"
170
+ />
167
171
  </template>
168
172
  `,
169
173
  };
@@ -8,13 +8,14 @@ import {
8
8
 
9
9
  import UInput from "../../ui.form-input/UInput.vue";
10
10
  import UIcon from "../../ui.image-icon/UIcon.vue";
11
- import UButton from "../../ui.button/UButton.vue";
12
11
  import UCol from "../../ui.container-col/UCol.vue";
13
12
  import URow from "../../ui.container-row/URow.vue";
14
- import UAvatar from "../../ui.image-avatar/UAvatar.vue";
13
+ import UDropdownButton from "../../ui.dropdown-button/UDropdownButton.vue";
14
+ import UText from "../../ui.text-block/UText.vue";
15
15
 
16
16
  import type { Meta, StoryFn } from "@storybook/vue3";
17
17
  import type { Props } from "../types.ts";
18
+ import { ref } from "vue";
18
19
 
19
20
  interface UInputArgs extends Props {
20
21
  slotTemplate?: string;
@@ -177,23 +178,49 @@ export const IconProps: StoryFn<UInputArgs> = (args) => ({
177
178
  });
178
179
 
179
180
  export const Slots: StoryFn<UInputArgs> = (args) => ({
180
- components: { UInput, URow, UButton, UAvatar },
181
+ components: { UInput, URow, UDropdownButton, UText },
181
182
  setup() {
182
- return { args };
183
+ const countryCodes = [
184
+ { label: "+33", id: "+33" },
185
+ { label: "+44", id: "+44" },
186
+ { label: "+49", id: "+49" },
187
+ ];
188
+
189
+ const countryCode = ref("+33");
190
+
191
+ return { args, countryCode, countryCodes };
183
192
  },
184
193
  template: `
185
194
  <URow>
186
- <UInput v-bind="args">
195
+ <UInput
196
+ label="Phone Number"
197
+ placeholder="Enter your phone number"
198
+ :config="{ leftSlot: 'pl-0' }"
199
+ >
187
200
  <template #left>
188
- <UAvatar />
201
+ <UDropdownButton
202
+ v-model="countryCode"
203
+ :options="countryCodes"
204
+ square
205
+ size="sm"
206
+ variant="ghost"
207
+ class="rounded-r-none h-[49px]"
208
+ />
189
209
  </template>
190
210
  </UInput>
191
211
 
192
- <UInput v-bind="args" :config="{ rightSlot: 'pr-0' }">
212
+ <UInput label="Website" placeholder="Enter your website">
193
213
  <template #right>
194
- <UButton label="Search" size="sm" class="rounded-l-none h-full" />
214
+ <UText label=".com" variant="lifted" />
195
215
  </template>
196
216
  </UInput>
197
217
  </URow>
198
218
  `,
199
219
  });
220
+ Slots.parameters = {
221
+ docs: {
222
+ story: {
223
+ height: "250px",
224
+ },
225
+ },
226
+ };
@@ -333,15 +333,16 @@ const {
333
333
  :removable="multiple && !disabled"
334
334
  @remove="onClickRemoveItem"
335
335
  >
336
- <template #default="{ id, label, url, imageUrl }">
336
+ <template #file="{ id, label, url, imageUrl, index }">
337
337
  <!--
338
338
  @slot Use it to add a file directly.
339
339
  @binding {string | number} id
340
340
  @binding {string} label
341
341
  @binding {string} url
342
342
  @binding {string} image-url
343
+ @binding {number} index
343
344
  -->
344
- <slot :id="id" :label="label" :url="url" :image-url="imageUrl" />
345
+ <slot :id="id" :label="label" :url="url" :image-url="imageUrl" :index="index" />
345
346
  </template>
346
347
  </UFiles>
347
348
 
@@ -10,6 +10,8 @@ import UInputFile from "../../ui.form-input-file/UInputFile.vue";
10
10
  import UCol from "../../ui.container-col/UCol.vue";
11
11
  import UBadge from "../../ui.text-badge/UBadge.vue";
12
12
  import UIcon from "../../ui.image-icon/UIcon.vue";
13
+ import URow from "../../ui.container-row/URow.vue";
14
+ import UText from "../../ui.text-block/UText.vue";
13
15
 
14
16
  import type { Meta, StoryFn } from "@storybook/vue3";
15
17
  import type { Props } from "../types.ts";
@@ -84,7 +86,7 @@ MaxFileSize.args = {
84
86
 
85
87
  export const AllowedFileTypes = DefaultTemplate.bind({});
86
88
  AllowedFileTypes.args = {
87
- allowedFileTypes: ["png", "jpeg"],
89
+ allowedFileTypes: [".png", ".jpeg"],
88
90
  description: "Only png and jpeg formats are allowed.",
89
91
  };
90
92
 
@@ -104,39 +106,53 @@ LabelSlot.args = {
104
106
  };
105
107
 
106
108
  export const Slots: StoryFn<UInputFileArgs> = (args) => ({
107
- components: { UInputFile, UCol, UBadge, UIcon },
109
+ components: { UInputFile, UCol, UBadge, UIcon, URow, UText },
108
110
  setup() {
109
111
  return { args };
110
112
  },
111
113
  template: `
112
- <UCol>
114
+ <UCol gap="xl">
113
115
  <UInputFile
114
116
  v-bind="args"
115
117
  v-model="args.files"
116
- label="Slot Top"
118
+ label="Top Slot"
119
+ :allowedFileTypes="['.jpeg', '.png']"
120
+ :maxFileSize="2"
117
121
  >
118
122
  <template #top>
119
- <UBadge label="Pending Review..." />
123
+ <URow align="center" gap="xs">
124
+ <UIcon name="info" size="sm" />
125
+ <UText variant="lifted">Recommended size: 400x400px, max 2MB</UText>
126
+ </URow>
120
127
  </template>
121
128
  </UInputFile>
122
129
 
123
130
  <UInputFile
124
131
  v-bind="args"
125
132
  v-model="args.files"
126
- label="Slot Left"
133
+ label="Left Slot"
134
+ :allowedFileTypes="['.pdf', '.doc', '.docx']"
127
135
  >
128
136
  <template #left>
129
- <UIcon name="info" color="warning" />
137
+ <URow align="center" gap="xs">
138
+ <UIcon name="description" size="sm" />
139
+ <UText label="PDF, DOC, DOCX" variant="lifted" size="xs" :wrap="false" />
140
+ </URow>
130
141
  </template>
131
142
  </UInputFile>
132
143
 
133
144
  <UInputFile
134
145
  v-bind="args"
135
146
  v-model="args.files"
136
- label="Slot Bottom"
147
+ label="Bottom Slot"
148
+ multiple
149
+ :allowedFileTypes="['.png', '.jpeg']"
137
150
  >
138
151
  <template #bottom>
139
- <UBadge label="An antivirus check will be performed after file upload." />
152
+ <URow align="center" gap="xs">
153
+ <UIcon name="schedule" size="sm" />
154
+ <UText label="Processing may take a few moments for multiple files" variant="lifted" />
155
+ </URow>
140
156
  </template>
141
157
  </UInputFile>
142
158
  </UCol>
@@ -1,3 +1,4 @@
1
+ import { ref } from "vue";
1
2
  import {
2
3
  getArgs,
3
4
  getArgTypes,
@@ -11,7 +12,8 @@ import UCol from "../../ui.container-col/UCol.vue";
11
12
  import UIcon from "../../ui.image-icon/UIcon.vue";
12
13
  import UButton from "../../ui.button/UButton.vue";
13
14
  import URow from "../../ui.container-row/URow.vue";
14
- import UAvatar from "../../ui.image-avatar/UAvatar.vue";
15
+ import UDropdownButton from "../../ui.dropdown-button/UDropdownButton.vue";
16
+ import UText from "../../ui.text-block/UText.vue";
15
17
 
16
18
  import type { Meta, StoryFn } from "@storybook/vue3";
17
19
  import type { Props } from "../types.ts";
@@ -185,23 +187,51 @@ export const IconProps: StoryFn<UInputNumberArgs> = (args) => ({
185
187
  });
186
188
 
187
189
  export const Slots: StoryFn<UInputNumberArgs> = (args) => ({
188
- components: { UInputNumber, URow, UButton, UAvatar },
190
+ components: { UInputNumber, URow, UButton, UDropdownButton, UText },
189
191
  setup() {
190
- return { args };
192
+ const currencies = [
193
+ { label: "USD", id: "usd" },
194
+ { label: "EUR", id: "eur" },
195
+ { label: "UAH", id: "uah" },
196
+ ];
197
+
198
+ const currency = ref("usd");
199
+
200
+ return { args, currency, currencies };
191
201
  },
192
202
  template: `
193
- <URow>
194
- <UInputNumber v-bind="args">
203
+ <URow class="gap-4">
204
+ <UInputNumber
205
+ label="Left slot"
206
+ placeholder="Enter discount amount"
207
+ :config="{ numberInput: { leftSlot: 'pl-0' } }"
208
+ >
195
209
  <template #left>
196
- <UAvatar />
210
+ <UDropdownButton
211
+ v-model="currency"
212
+ :options="currencies"
213
+ size="sm"
214
+ variant="ghost"
215
+ class="rounded-r-none h-[49px]"
216
+ />
197
217
  </template>
198
218
  </UInputNumber>
199
219
 
200
- <UInputNumber v-bind="args" :config="{ moneyInput: { rightSlot: 'pr-0' } }">
220
+ <UInputNumber
221
+ label="Right slot"
222
+ placeholder="Enter your annual payment"
223
+ >
201
224
  <template #right>
202
- <UButton label="Calculate" size="sm" class="rounded-l-none h-full" />
225
+ <UText label="%, per year" variant="lifted" size="sm" :wrap="false" />
203
226
  </template>
204
227
  </UInputNumber>
205
228
  </URow>
206
229
  `,
207
230
  });
231
+ Slots.parameters = {
232
+ docs: {
233
+ story: {
234
+ height: "250px",
235
+ },
236
+ },
237
+ };
@@ -10,7 +10,7 @@ import UInputPassword from "../UInputPassword.vue";
10
10
  import UCol from "../../ui.container-col/UCol.vue";
11
11
  import URow from "../../ui.container-row/URow.vue";
12
12
  import UButton from "../../ui.button/UButton.vue";
13
- import UAvatar from "../../ui.image-avatar/UAvatar.vue";
13
+ import UDropdownButton from "../../ui.dropdown-button/UDropdownButton.vue";
14
14
 
15
15
  import type { Meta, StoryFn } from "@storybook/vue3";
16
16
  import type { Props } from "../types.ts";
@@ -126,25 +126,39 @@ export const IconProps: StoryFn<UInputPasswordArgs> = (args) => ({
126
126
  });
127
127
 
128
128
  export const Slots: StoryFn<UInputPasswordArgs> = (args) => ({
129
- components: { UInputPassword, URow, UButton, UAvatar },
129
+ components: { UInputPassword, URow, UButton, UDropdownButton },
130
130
  setup() {
131
- return { args };
131
+ const wifiTypes = [
132
+ { label: "WPA2", id: "wpa2" },
133
+ { label: "WPA3", id: "wpa3" },
134
+ ];
135
+
136
+ return { args, wifiTypes };
132
137
  },
133
138
  template: `
134
- <URow>
139
+ <URow align="stretch">
135
140
  <UInputPassword
136
141
  v-bind="args"
137
142
  v-model="args.modelValue"
143
+ placeholder="Enter your password"
138
144
  :config="{ passwordInput: { leftSlot: 'pl-0' } }"
139
145
  >
140
146
  <template #left>
141
- <UAvatar />
147
+ <UDropdownButton
148
+ v-model="args.wifiType"
149
+ :options="wifiTypes"
150
+ label="Wifi type"
151
+ size="sm"
152
+ variant="ghost"
153
+ class="rounded-r-none h-[49px]"
154
+ />
142
155
  </template>
143
156
  </UInputPassword>
144
157
 
145
158
  <UInputPassword
146
159
  v-bind="args"
147
160
  v-model="args.modelValue"
161
+ placeholder="Enter your password"
148
162
  :config="{ passwordInput: { rightSlot: 'pr-0' } }"
149
163
  >
150
164
  <template #right="{ visible, toggle }">
@@ -153,7 +167,7 @@ export const Slots: StoryFn<UInputPasswordArgs> = (args) => ({
153
167
  color="neutral"
154
168
  variant="ghost"
155
169
  size="sm"
156
- class="rounded-l-none h-full"
170
+ class="rounded-l-none h-[49px] min-w-[69px]"
157
171
  @click="toggle"
158
172
  />
159
173
  </template>
@@ -161,3 +175,10 @@ export const Slots: StoryFn<UInputPasswordArgs> = (args) => ({
161
175
  </URow>
162
176
  `,
163
177
  });
178
+ Slots.parameters = {
179
+ docs: {
180
+ story: {
181
+ height: "200px",
182
+ },
183
+ },
184
+ };
@@ -116,9 +116,17 @@ RatingIcons.parameters = {
116
116
 
117
117
  export const CounterSlot = DefaultTemplate.bind({});
118
118
  CounterSlot.args = {
119
+ counter: true,
120
+ stars: 5,
121
+ modelValue: 0,
119
122
  slotTemplate: `
120
123
  <template #counter="{ counter }">
121
- <UBadge :label="'Current rating is: ' + String(counter)" color="success" />
124
+ <UBadge
125
+ :label="counter === 0
126
+ ? 'No rating yet'
127
+ : counter + ' out of ' + args.stars + ' stars'"
128
+ :color="counter ? 'success' : 'warning'"
129
+ />
122
130
  </template>
123
131
  `,
124
132
  };
@@ -127,8 +135,12 @@ export const TotalSlot = DefaultTemplate.bind({});
127
135
  TotalSlot.args = {
128
136
  total: 250,
129
137
  slotTemplate: `
130
- <template #total="{total}">
131
- <UBadge :label="'Total reviews: ' + String(total)" color="success" />
138
+ <template #total="{ total }">
139
+ <UBadge
140
+ :label="total + ' reviews'"
141
+ color="info"
142
+ size="sm"
143
+ />
132
144
  </template>
133
145
  `,
134
146
  };
@@ -7,12 +7,11 @@ import {
7
7
  } from "../../utils/storybook.ts";
8
8
 
9
9
  import UInputSearch from "../../ui.form-input-search/UInputSearch.vue";
10
- import UButton from "../../ui.button/UButton.vue";
11
10
  import UIcon from "../../ui.image-icon/UIcon.vue";
12
11
  import UCol from "../../ui.container-col/UCol.vue";
13
12
  import URow from "../../ui.container-row/URow.vue";
14
- import UAvatar from "../../ui.image-avatar/UAvatar.vue";
15
- import UBadge from "../../ui.text-badge/UBadge.vue";
13
+ import UDropdownButton from "../../ui.dropdown-button/UDropdownButton.vue";
14
+ import UText from "../../ui.text-block/UText.vue";
16
15
 
17
16
  import type { Meta, StoryFn } from "@storybook/vue3";
18
17
  import type { Props } from "../types.ts";
@@ -40,7 +39,7 @@ export default {
40
39
  } as Meta;
41
40
 
42
41
  const DefaultTemplate: StoryFn<UInputSearchArgs> = (args: UInputSearchArgs) => ({
43
- components: { UInputSearch, UButton, UIcon },
42
+ components: { UInputSearch, UIcon },
44
43
  setup: () => ({ args, slots: getSlotNames(UInputSearch.__name) }),
45
44
  template: `
46
45
  <UInputSearch
@@ -136,23 +135,49 @@ export const IconProps: StoryFn<UInputSearchArgs> = (args) => ({
136
135
  });
137
136
 
138
137
  export const Slots: StoryFn<UInputSearchArgs> = (args) => ({
139
- components: { UInputSearch, URow, UButton, UAvatar, UBadge },
138
+ components: { UInputSearch, UCol, URow, UIcon, UDropdownButton, UText },
140
139
  setup() {
141
- return { args };
140
+ const aiVersions = [
141
+ { label: "GPT-4o", id: "gpt-4o" },
142
+ { label: "GPT-4o-mini", id: "gpt-4o-mini" },
143
+ { label: "GPT-4", id: "gpt-4" },
144
+ ];
145
+
146
+ return { args, aiVersions };
142
147
  },
143
148
  template: `
144
- <URow>
145
- <UInputSearch v-bind="args" :config="{ searchInput: { leftSlot: 'pl-0' } }">
146
- <template #left>
147
- <UAvatar size="sm" />
149
+ <UCol>
150
+ <UInputSearch placeholder="Search by rental district...">
151
+ <template #right>
152
+ <URow align="center" gap="xs">
153
+ <UIcon name="straighten" size="sm" />
154
+ <UText label="+2km" variant="muted" />
155
+ </URow>
148
156
  </template>
149
157
  </UInputSearch>
150
158
 
151
- <UInputSearch v-bind="args" :config="{ searchInput: { rightSlot: 'pr-0' } }">
152
- <template #right>
153
- <UBadge label="Search" size="sm" color="success" />
159
+ <UInputSearch
160
+ placeholder="Ask something..."
161
+ :config="{ searchInput: { leftSlot: 'pl-0' } }"
162
+ >
163
+ <template #left>
164
+ <UDropdownButton
165
+ v-model="args.aiVersion"
166
+ :options="aiVersions"
167
+ label="AI Version"
168
+ size="sm"
169
+ variant="ghost"
170
+ class="rounded-r-none"
171
+ />
154
172
  </template>
155
173
  </UInputSearch>
156
- </URow>
174
+ </UCol>
157
175
  `,
158
176
  });
177
+ Slots.parameters = {
178
+ docs: {
179
+ story: {
180
+ height: "270px",
181
+ },
182
+ },
183
+ };
@@ -1,4 +1,3 @@
1
- import { ref } from "vue";
2
1
  import {
3
2
  getArgs,
4
3
  getArgTypes,
@@ -11,8 +10,8 @@ import UTextarea from "../../ui.form-textarea/UTextarea.vue";
11
10
  import UIcon from "../../ui.image-icon/UIcon.vue";
12
11
  import UCol from "../../ui.container-col/UCol.vue";
13
12
  import URow from "../../ui.container-row/URow.vue";
14
- import UAvatar from "../../ui.image-avatar/UAvatar.vue";
15
13
  import tooltip from "../../directives/tooltip/vTooltip.ts";
14
+ import UText from "../../ui.text-block/UText.vue";
16
15
 
17
16
  import type { Meta, StoryFn } from "@storybook/vue3";
18
17
  import type { Props } from "../types.ts";
@@ -123,18 +122,14 @@ NoAutocomplete.parameters = {
123
122
  };
124
123
 
125
124
  export const Slots: StoryFn<UTextareaArgs> = (args) => ({
126
- components: { UTextarea, URow, UIcon, UAvatar },
125
+ components: { UTextarea, URow, UIcon, UText },
127
126
  directives: { tooltip },
128
- setup() {
129
- const switchModel = ref(false);
130
-
131
- return { args, switchModel };
132
- },
127
+ setup: () => ({ args }),
133
128
  template: `
134
129
  <URow>
135
- <UTextarea v-bind="args">
130
+ <UTextarea v-bind="args" v-model="args.modelValue" :max-length="300">
136
131
  <template #left>
137
- <UAvatar />
132
+ <UText :label="args.modelValue?.length + '/300'" variant="lifted" />
138
133
  </template>
139
134
  </UTextarea>
140
135
 
@@ -3,7 +3,6 @@ import { useTemplateRef } from "vue";
3
3
 
4
4
  import useUI from "../composables/useUI.ts";
5
5
  import { getDefaults } from "../utils/ui.ts";
6
- import { hasSlotContent } from "../utils/helper.ts";
7
6
 
8
7
  import { COMPONENT_NAME } from "./constants.ts";
9
8
  import defaultConfig from "./config.ts";
@@ -30,13 +29,14 @@ defineExpose({
30
29
  * Get element / nested component attributes for each config token ✨
31
30
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
32
31
  */
33
- const { getDataTest, wrapperAttrs, htmlAttrs } = useUI<Config>(defaultConfig);
32
+ const { getDataTest, wrapperAttrs, labelAttrs } = useUI<Config>(defaultConfig);
34
33
  </script>
35
34
 
36
35
  <template>
37
36
  <div ref="wrapper" v-bind="wrapperAttrs" :data-test="getDataTest()">
38
37
  <!-- @slot Use it to add something inside. -->
39
- <div v-if="!hasSlotContent($slots['default'])" v-bind="htmlAttrs" v-html="html" />
40
- <slot />
38
+ <slot>
39
+ <div v-bind="labelAttrs" v-text="label" />
40
+ </slot>
41
41
  </div>
42
42
  </template>
@@ -12,6 +12,15 @@ export default /*tw*/ {
12
12
  [&_ul]:ml-2 [&_ol]:ml-2
13
13
  `,
14
14
  variants: {
15
+ variant: {
16
+ default: "text-{color}",
17
+ lifted: "text-{color}-lifted",
18
+ accented: "text-{color}-accented",
19
+ muted: "text-{color}/(--vl-disabled-opacity)",
20
+ },
21
+ color: {
22
+ inherit: "text-inherit",
23
+ },
15
24
  size: {
16
25
  xs: "text-tiny space-y-1",
17
26
  sm: "text-small space-y-2",
@@ -26,12 +35,24 @@ export default /*tw*/ {
26
35
  line: {
27
36
  true: "leading-none",
28
37
  },
38
+ wrap: {
39
+ false: "text-nowrap",
40
+ },
29
41
  },
42
+ compoundVariants: [
43
+ { color: "text", variant: "default", class: "text-default" },
44
+ { color: "text", variant: "lifted", class: "text-lifted" },
45
+ { color: "text", variant: "accented", class: "text-accented" },
46
+ { color: "text", variant: "muted", class: "text-muted" },
47
+ ],
30
48
  },
31
- html: "",
49
+ label: "",
32
50
  defaults: {
51
+ color: "text",
52
+ variant: "default",
33
53
  size: "md",
34
54
  align: "left",
35
55
  line: false,
56
+ wrap: true,
36
57
  },
37
58
  };
@@ -1,10 +1,10 @@
1
1
  import {
2
+ trimText,
2
3
  getArgs,
3
4
  getArgTypes,
4
5
  getSlotNames,
5
6
  getSlotsFragment,
6
7
  getDocsDescription,
7
- getEnumVariantDescription,
8
8
  } from "../../utils/storybook.ts";
9
9
 
10
10
  import UText from "../../ui.text-block/UText.vue";
@@ -12,20 +12,23 @@ import URow from "../../ui.container-row/URow.vue";
12
12
  import UCol from "../../ui.container-col/UCol.vue";
13
13
  import UBadge from "../../ui.text-badge/UBadge.vue";
14
14
 
15
- import tooltip from "../../directives/tooltip/vTooltip.ts";
16
-
17
15
  import type { Meta, StoryFn } from "@storybook/vue3";
18
16
  import type { Props } from "../types.ts";
19
17
 
20
18
  interface UTextArgs extends Props {
21
19
  slotTemplate?: string;
22
- enum: "size" | "align";
20
+ enum: "size" | "align" | "variant" | "color";
23
21
  }
24
22
 
25
23
  export default {
26
24
  id: "4020",
27
25
  title: "Text & Content / Text",
28
26
  component: UText,
27
+ args: {
28
+ label: trimText(`
29
+ Easily customize your UI with flexible components.
30
+ `),
31
+ },
29
32
  argTypes: {
30
33
  ...getArgTypes(UText.__name),
31
34
  },
@@ -34,29 +37,20 @@ export default {
34
37
  ...getDocsDescription(UText.__name),
35
38
  },
36
39
  },
37
- args: {},
38
40
  } as Meta;
39
41
 
40
- const defaultTemplate = `
41
- <p>
42
- <b>To proceed with your registration</b>, please enter your <u>email address</u> in the field below.
43
- <i>A verification link</i> will be sent to your inbox shortly.
44
- </p>
45
- `;
46
-
47
42
  const DefaultTemplate: StoryFn<UTextArgs> = (args: UTextArgs) => ({
48
43
  components: { UText, URow, UBadge },
49
44
  setup: () => ({ args, slots: getSlotNames(UText.__name) }),
50
45
  template: `
51
46
  <UText v-bind="args">
52
- ${args.slotTemplate || getSlotsFragment(defaultTemplate)}
47
+ ${args.slotTemplate || getSlotsFragment("")}
53
48
  </UText>
54
49
  `,
55
50
  });
56
51
 
57
52
  const EnumTemplate: StoryFn<UTextArgs> = (args: UTextArgs, { argTypes }) => ({
58
53
  components: { UText, UCol },
59
- directives: { tooltip },
60
54
  setup: () => ({ args, argTypes, getArgs }),
61
55
  template: `
62
56
  <UCol>
@@ -64,11 +58,8 @@ const EnumTemplate: StoryFn<UTextArgs> = (args: UTextArgs, { argTypes }) => ({
64
58
  v-for="option in argTypes?.[args.enum]?.options"
65
59
  v-bind="getArgs(args, option)"
66
60
  :key="option"
67
- v-tooltip="option"
68
61
  class="w-full"
69
- >
70
- ${args.slotTemplate || defaultTemplate}
71
- </UText>
62
+ />
72
63
  </UCol>
73
64
  `,
74
65
  });
@@ -78,23 +69,57 @@ Default.args = {};
78
69
 
79
70
  export const Align = EnumTemplate.bind({});
80
71
  Align.args = { enum: "align" };
81
- Align.parameters = getEnumVariantDescription();
82
72
 
83
73
  export const Sizes = EnumTemplate.bind({});
84
74
  Sizes.args = { enum: "size" };
85
- Sizes.parameters = getEnumVariantDescription();
86
75
 
87
- export const Line = DefaultTemplate.bind({});
88
- Line.args = {
89
- line: true,
90
- };
91
- Line.parameters = {
92
- docs: {
93
- description: {
94
- story: "Removes text line height (useful for 1-line text).",
95
- },
96
- },
97
- };
76
+ export const Color = EnumTemplate.bind({});
77
+ Color.args = { enum: "color" };
78
+
79
+ export const Variant = EnumTemplate.bind({});
80
+ Variant.args = { enum: "variant" };
81
+
82
+ export const Line: StoryFn<UTextArgs> = (args: UTextArgs) => ({
83
+ components: { UText, UCol },
84
+ setup: () => ({ args }),
85
+ template: `
86
+ <UCol>
87
+ <UText>
88
+ <div class="rounded-medium border border-primary border-dashed w-fit">
89
+ Text with default library line height.
90
+ </div>
91
+ </UText>
92
+
93
+ <UText line>
94
+ <div class="rounded-medium border border-primary border-dashed w-fit">
95
+ Text with line height equal to its font size.
96
+ </div>
97
+ </UText>
98
+ </UCol>
99
+ `,
100
+ });
101
+ Line.args = {};
102
+
103
+ export const Wrap: StoryFn<UTextArgs> = (args: UTextArgs) => ({
104
+ components: { UText, UCol },
105
+ setup: () => ({ args }),
106
+ template: `
107
+ <UCol>
108
+ <UText>
109
+ <div class="rounded-medium border border-primary border-dashed w-32">
110
+ Text with wrapping enabled (default behavior).
111
+ </div>
112
+ </UText>
113
+
114
+ <UText :wrap="false">
115
+ <div class="rounded-medium border border-primary border-dashed w-32">
116
+ Text with wrapping disabled (text-nowrap).
117
+ </div>
118
+ </UText>
119
+ </UCol>
120
+ `,
121
+ });
122
+ Wrap.args = {};
98
123
 
99
124
  export const Paragraphs = DefaultTemplate.bind({});
100
125
  Paragraphs.args = {
@@ -141,11 +166,21 @@ List.args = {
141
166
  `,
142
167
  };
143
168
 
144
- export const DefaultSlot = DefaultTemplate.bind({});
145
- DefaultSlot.args = {
169
+ export const FormattedText = DefaultTemplate.bind({});
170
+ FormattedText.args = {
171
+ slotTemplate: `
172
+ <p>
173
+ <b>To proceed with your registration</b>, please enter your <u>email address</u> in the field below.
174
+ <i>A verification link</i> will be sent to your inbox shortly.
175
+ </p>
176
+ `,
177
+ };
178
+
179
+ export const NestedComponent = DefaultTemplate.bind({});
180
+ NestedComponent.args = {
146
181
  slotTemplate: `
147
182
  <p>To proceed with your registration, please enter your
148
- <UBadge label="email address" v-bind="args" color="success" /> in the field below.
183
+ <UBadge label="email address" color="success" variant="subtle" /> in the field below.
149
184
  A verification link will be sent to your inbox shortly.</p>
150
185
  `,
151
186
  };
@@ -47,6 +47,56 @@ describe("UText.vue", () => {
47
47
  });
48
48
  });
49
49
 
50
+ // Color prop
51
+ it("applies the correct color class", async () => {
52
+ const colors = [
53
+ "primary",
54
+ "secondary",
55
+ "error",
56
+ "warning",
57
+ "success",
58
+ "info",
59
+ "notice",
60
+ "neutral",
61
+ "grayscale",
62
+ "inherit",
63
+ "text",
64
+ ];
65
+
66
+ colors.forEach((color) => {
67
+ const component = mount(UText, {
68
+ props: {
69
+ color: color as Props["color"],
70
+ },
71
+ });
72
+
73
+ color === "text"
74
+ ? expect(component.attributes("class")).toContain("text-default")
75
+ : expect(component.attributes("class")).toContain(color);
76
+ });
77
+ });
78
+
79
+ // Variant prop
80
+ it("applies the correct variant class", async () => {
81
+ const variants = {
82
+ default: "text-primary",
83
+ lifted: "text-primary-lifted",
84
+ accented: "text-primary-accented",
85
+ muted: "text-primary/(--vl-disabled-opacity)",
86
+ };
87
+
88
+ Object.entries(variants).forEach(([variant, classes]) => {
89
+ const component = mount(UText, {
90
+ props: {
91
+ variant: variant as Props["variant"],
92
+ color: "primary",
93
+ },
94
+ });
95
+
96
+ expect(component.attributes("class")).toContain(classes);
97
+ });
98
+ });
99
+
50
100
  // Line prop
51
101
  it("applies line class when line prop is true", () => {
52
102
  const line = true;
@@ -60,17 +110,30 @@ describe("UText.vue", () => {
60
110
  expect(component.attributes("class")).toContain("leading-none");
61
111
  });
62
112
 
63
- // HTML prop
64
- it("renders the correct HTML content", () => {
65
- const html = "<strong>Bold Text</strong>";
113
+ // Wrap prop
114
+ it("applies text-nowrap class when wrap prop is false", () => {
115
+ const wrap = false;
116
+
117
+ const component = mount(UText, {
118
+ props: {
119
+ wrap,
120
+ },
121
+ });
122
+
123
+ expect(component.attributes("class")).toContain("text-nowrap");
124
+ });
125
+
126
+ // Label prop
127
+ it("renders the correct label content", () => {
128
+ const label = "Text label";
66
129
 
67
130
  const component = mount(UText, {
68
131
  props: {
69
- html,
132
+ label,
70
133
  },
71
134
  });
72
135
 
73
- expect(component.html()).toContain(html);
136
+ expect(component.html()).toContain(label);
74
137
  });
75
138
 
76
139
  // DataTest prop
@@ -88,7 +151,7 @@ describe("UText.vue", () => {
88
151
 
89
152
  // Config prop overriding classes
90
153
  it("applies custom classes from config prop", () => {
91
- const customClasses = "text-red-500 font-bold";
154
+ const customClasses = "font-bold";
92
155
 
93
156
  const component = mount(UText, {
94
157
  props: {
@@ -117,14 +180,14 @@ describe("UText.vue", () => {
117
180
  expect(component.text()).toContain(slotContent);
118
181
  });
119
182
 
120
- // Default slot overrides html prop
121
- it("default slot overrides html prop", () => {
122
- const html = "<strong>Bold Text</strong>";
183
+ // Default slot overrides label prop
184
+ it("default slot overrides label prop", () => {
185
+ const label = "Label Text";
123
186
  const slotContent = "Custom Content";
124
187
 
125
188
  const component = mount(UText, {
126
189
  props: {
127
- html,
190
+ label,
128
191
  },
129
192
  slots: {
130
193
  default: slotContent,
@@ -132,7 +195,7 @@ describe("UText.vue", () => {
132
195
  });
133
196
 
134
197
  expect(component.text()).toContain(slotContent);
135
- expect(component.html()).not.toContain(html);
198
+ expect(component.html()).not.toContain(label);
136
199
  });
137
200
  });
138
201
 
@@ -5,9 +5,9 @@ export type Config = typeof defaultConfig;
5
5
 
6
6
  export interface Props {
7
7
  /**
8
- * HTML markdown or plain text.
8
+ * Text label.
9
9
  */
10
- html?: string;
10
+ label?: string;
11
11
 
12
12
  /**
13
13
  * Text size.
@@ -19,11 +19,37 @@ export interface Props {
19
19
  */
20
20
  align?: "left" | "center" | "right";
21
21
 
22
+ /**
23
+ * Text variant.
24
+ */
25
+ variant?: "default" | "accented" | "lifted" | "muted";
26
+
27
+ /**
28
+ * Text color.
29
+ */
30
+ color?:
31
+ | "primary"
32
+ | "secondary"
33
+ | "error"
34
+ | "warning"
35
+ | "success"
36
+ | "info"
37
+ | "notice"
38
+ | "neutral"
39
+ | "grayscale"
40
+ | "inherit"
41
+ | "text"; // the default design system text color
42
+
22
43
  /**
23
44
  * Removes text line height (useful for 1-line text).
24
45
  */
25
46
  line?: boolean;
26
47
 
48
+ /**
49
+ * Enables text wrapping.
50
+ */
51
+ wrap?: boolean;
52
+
27
53
  /**
28
54
  * Component config object.
29
55
  */
@@ -35,6 +35,7 @@ const { getDataTest, headerAttrs } = useUI<Config>(defaultConfig);
35
35
 
36
36
  <template>
37
37
  <component :is="tag" ref="header" v-bind="headerAttrs" :data-test="getDataTest()">
38
+ <!-- @slot Use it to add html inside. -->
38
39
  <slot>{{ label }}</slot>
39
40
  </component>
40
41
  </template>
@@ -1,7 +1,16 @@
1
1
  export default /*tw*/ {
2
2
  header: {
3
- base: "text-{color} font-medium",
3
+ base: "font-medium",
4
4
  variants: {
5
+ variant: {
6
+ default: "text-{color}",
7
+ lifted: "text-{color}-lifted",
8
+ accented: "text-{color}-accented",
9
+ muted: "text-{color}/(--vl-disabled-opacity)",
10
+ },
11
+ color: {
12
+ inherit: "text-inherit",
13
+ },
5
14
  size: {
6
15
  xs: "text-lg",
7
16
  sm: "text-xl",
@@ -14,9 +23,16 @@ export default /*tw*/ {
14
23
  true: "!leading-none",
15
24
  },
16
25
  },
26
+ compoundVariants: [
27
+ { color: "text", variant: "default", class: "text-default" },
28
+ { color: "text", variant: "lifted", class: "text-lifted" },
29
+ { color: "text", variant: "accented", class: "text-accented" },
30
+ { color: "text", variant: "muted", class: "text-muted" },
31
+ ],
17
32
  },
18
33
  defaults: {
19
- color: "grayscale",
34
+ color: "text",
35
+ variant: "default",
20
36
  size: "md",
21
37
  tag: "div",
22
38
  line: true,
@@ -18,7 +18,7 @@ import type { Props } from "../types.ts";
18
18
 
19
19
  interface UHeaderArgs extends Props {
20
20
  slotTemplate?: string;
21
- enum: "size" | "color";
21
+ enum: "size" | "color" | "variant";
22
22
  }
23
23
 
24
24
  export default {
@@ -71,6 +71,10 @@ export const Sizes = EnumTemplate.bind({});
71
71
  Sizes.args = { enum: "size" };
72
72
  Sizes.parameters = getEnumVariantDescription();
73
73
 
74
+ export const Variants = EnumTemplate.bind({});
75
+ Variants.args = { enum: "variant" };
76
+ Variants.parameters = getEnumVariantDescription();
77
+
74
78
  export const Colors = EnumTemplate.bind({});
75
79
  Colors.args = { enum: "color" };
76
80
  Colors.parameters = getEnumVariantDescription();
@@ -42,6 +42,7 @@ describe("UHeader.vue", () => {
42
42
  "notice",
43
43
  "neutral",
44
44
  "grayscale",
45
+ "text",
45
46
  ];
46
47
 
47
48
  colors.forEach((color) => {
@@ -51,7 +52,30 @@ describe("UHeader.vue", () => {
51
52
  },
52
53
  });
53
54
 
54
- expect(component.attributes("class")).toContain(color);
55
+ color === "text"
56
+ ? expect(component.attributes("class")).toContain("text-default")
57
+ : expect(component.attributes("class")).toContain(color);
58
+ });
59
+ });
60
+
61
+ // Variant prop
62
+ it("applies the correct variant class", async () => {
63
+ const variants = {
64
+ default: "text-primary",
65
+ lifted: "text-primary-lifted",
66
+ accented: "text-primary-accented",
67
+ muted: "text-primary/(--vl-disabled-opacity)",
68
+ };
69
+
70
+ Object.entries(variants).forEach(([variant, classes]) => {
71
+ const component = mount(UHeader, {
72
+ props: {
73
+ variant: variant as Props["variant"],
74
+ color: "primary",
75
+ },
76
+ });
77
+
78
+ expect(component.attributes("class")).toContain(classes);
55
79
  });
56
80
  });
57
81
 
@@ -14,6 +14,11 @@ export interface Props {
14
14
  */
15
15
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
16
16
 
17
+ /**
18
+ * Text variant.
19
+ */
20
+ variant?: "default" | "accented" | "lifted" | "muted";
21
+
17
22
  /**
18
23
  * Header color.
19
24
  */
@@ -26,7 +31,8 @@ export interface Props {
26
31
  | "info"
27
32
  | "notice"
28
33
  | "neutral"
29
- | "grayscale";
34
+ | "grayscale"
35
+ | "text"; // the default design system text color
30
36
 
31
37
  /**
32
38
  * Allows changing HTML tag.
@@ -21,18 +21,18 @@ Use `notifySuccess`, `notifyWarning`, `notifyError` shortcut methods to trigger
21
21
 
22
22
  <Source code={`
23
23
  import {
24
- notify,
25
- notifySuccess,
26
- notifyWarning,
27
- notifyError,
24
+ notify,
25
+ notifySuccess,
26
+ notifyWarning,
27
+ notifyError,
28
28
  } from "vueless";
29
29
 
30
30
  notify({
31
- type: "success", // @values: success, warning, error
32
- label: "Hurray!",
33
- description: "The file successfully downloaded.",
34
- duration: 1000, // in milliseconds
35
- ignoreDuplicates: false // ignore notifications with the same 'description'
31
+ type: "success", // @values: success, warning, error
32
+ label: "Hurray!",
33
+ description: "The file successfully downloaded.",
34
+ duration: 1000, // in milliseconds
35
+ ignoreDuplicates: false // ignore notifications with the same 'description'
36
36
  });
37
37
 
38
38
  notifySuccess({ description: "The file successfully downloaded." });
@@ -52,18 +52,18 @@ notifySuccess({ description: "The file successfully downloaded." });
52
52
  clearNotifications();
53
53
  `} language="jsx" dark />
54
54
 
55
- ## Trigger notifications
55
+ ## Delayed notifications
56
56
  You can create a delayed notification which you can trigger any time later.
57
57
 
58
58
  <Source code={`
59
59
  import { setDelayedNotify, getDelayedNotify } from "vueless";
60
60
 
61
61
  setDelayedNotify({
62
- type: "success", // @values: success, warning, error
63
- label: "Hurray!",
64
- description: "The file successfully downloaded.",
65
- duration: 1000, // in milliseconds
66
- ignoreDuplicates: false // ignore notifications with the same 'description'
62
+ type: "success", // @values: success, warning, error
63
+ label: "Hurray!",
64
+ description: "The file successfully downloaded.",
65
+ duration: 1000, // in milliseconds
66
+ ignoreDuplicates: false // ignore notifications with the same 'description'
67
67
  });
68
68
 
69
69
  // and somewhere below
@@ -409,3 +409,7 @@ export function getEnumVariantDescription(message = "Hover over a variant to see
409
409
  },
410
410
  };
411
411
  }
412
+
413
+ export function trimText(text: string) {
414
+ return text.replace(/\s+/g, " ").trim();
415
+ }