vueless 1.3.5-beta.1 → 1.3.5-beta.2

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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/components.d.ts +2 -1
  3. package/components.ts +2 -1
  4. package/constants.d.ts +2 -1
  5. package/constants.js +2 -1
  6. package/package.json +2 -2
  7. package/types.ts +1 -1
  8. package/ui.container-col/config.ts +8 -0
  9. package/ui.container-col/tests/UCol.test.ts +26 -0
  10. package/ui.container-col/types.ts +10 -0
  11. package/{ui.text-empty → ui.container-empty}/storybook/stories.ts +7 -4
  12. package/ui.container-placeholder/UPlaceholder.vue +50 -0
  13. package/ui.container-placeholder/config.ts +40 -0
  14. package/ui.container-placeholder/constants.ts +5 -0
  15. package/ui.container-placeholder/storybook/docs.mdx +15 -0
  16. package/ui.container-placeholder/storybook/stories.ts +126 -0
  17. package/ui.container-placeholder/tests/UPlaceholder.test.ts +173 -0
  18. package/ui.container-placeholder/types.ts +56 -0
  19. package/ui.container-row/config.ts +8 -0
  20. package/ui.container-row/tests/URow.test.ts +26 -0
  21. package/ui.container-row/types.ts +10 -0
  22. package/ui.data-list/UDataList.vue +1 -1
  23. package/ui.data-list/tests/UDataList.test.ts +1 -1
  24. package/ui.data-table/UTable.vue +1 -1
  25. package/ui.data-table/tests/UTable.test.ts +1 -1
  26. /package/{ui.text-empty → ui.container-empty}/UEmpty.vue +0 -0
  27. /package/{ui.text-empty → ui.container-empty}/config.ts +0 -0
  28. /package/{ui.text-empty → ui.container-empty}/constants.ts +0 -0
  29. /package/{ui.text-empty → ui.container-empty}/storybook/assets/empty-inbox.png +0 -0
  30. /package/{ui.text-empty → ui.container-empty}/storybook/docs.mdx +0 -0
  31. /package/{ui.text-empty → ui.container-empty}/tests/UEmpty.test.ts +0 -0
  32. /package/{ui.text-empty → ui.container-empty}/types.ts +0 -0
package/README.md CHANGED
@@ -28,7 +28,7 @@ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [
28
28
  - 🖼️ Inline SVG icons
29
29
  - 🪄 Auto component imports (as you use them)
30
30
  - 🧿 Uncompiled source in npm for better DX
31
- - 🧪️ 1300+ unit tests ensuring consistent logic
31
+ - 🧪️ 1400+ unit tests ensuring consistent logic
32
32
  - 🛡️ Full TypeScript support with type safety
33
33
 
34
34
  ## Built-In Storybook
package/components.d.ts CHANGED
@@ -36,7 +36,6 @@ export { default as UNotify } from "./ui.text-notify/UNotify.vue";
36
36
  export { default as UNumber } from "./ui.text-number/UNumber.vue";
37
37
  export { default as UFile } from "./ui.text-file/UFile.vue";
38
38
  export { default as UFiles } from "./ui.text-files/UFiles.vue";
39
- export { default as UEmpty } from "./ui.text-empty/UEmpty.vue";
40
39
  export { default as UBadge } from "./ui.text-badge/UBadge.vue";
41
40
  /* Containers */
42
41
  export { default as UDivider } from "./ui.container-divider/UDivider.vue";
@@ -46,6 +45,8 @@ export { default as UGroup } from "./ui.container-group/UGroup.vue";
46
45
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
47
46
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
48
47
  export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
48
+ export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
49
+ export { default as UPlaceholder } from "./ui.container-placeholder/UPlaceholder.vue";
49
50
  export { default as UCard } from "./ui.container-card/UCard.vue";
50
51
  export { default as UModal } from "./ui.container-modal/UModal.vue";
51
52
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
package/components.ts CHANGED
@@ -36,7 +36,6 @@ export { default as UNotify } from "./ui.text-notify/UNotify.vue";
36
36
  export { default as UNumber } from "./ui.text-number/UNumber.vue";
37
37
  export { default as UFile } from "./ui.text-file/UFile.vue";
38
38
  export { default as UFiles } from "./ui.text-files/UFiles.vue";
39
- export { default as UEmpty } from "./ui.text-empty/UEmpty.vue";
40
39
  export { default as UBadge } from "./ui.text-badge/UBadge.vue";
41
40
  /* Containers */
42
41
  export { default as UDivider } from "./ui.container-divider/UDivider.vue";
@@ -46,6 +45,8 @@ export { default as UGroup } from "./ui.container-group/UGroup.vue";
46
45
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
47
46
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
48
47
  export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
48
+ export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
49
+ export { default as UPlaceholder } from "./ui.container-placeholder/UPlaceholder.vue";
49
50
  export { default as UCard } from "./ui.container-card/UCard.vue";
50
51
  export { default as UModal } from "./ui.container-modal/UModal.vue";
51
52
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
package/constants.d.ts CHANGED
@@ -176,7 +176,6 @@ export namespace COMPONENTS {
176
176
  let UNumber: string;
177
177
  let UFile: string;
178
178
  let UFiles: string;
179
- let UEmpty: string;
180
179
  let UBadge: string;
181
180
  let UDivider: string;
182
181
  let UCol: string;
@@ -185,6 +184,8 @@ export namespace COMPONENTS {
185
184
  let UGroups: string;
186
185
  let UAccordion: string;
187
186
  let UAccordionItem: string;
187
+ let UEmpty: string;
188
+ let UPlaceholder: string;
188
189
  let UCard: string;
189
190
  let UModal: string;
190
191
  let UModalConfirm: string;
package/constants.js CHANGED
@@ -284,7 +284,6 @@ export const COMPONENTS = {
284
284
  UNumber: "ui.text-number",
285
285
  UFile: "ui.text-file",
286
286
  UFiles: "ui.text-files",
287
- UEmpty: "ui.text-empty",
288
287
  UBadge: "ui.text-badge",
289
288
 
290
289
  /* Containers */
@@ -295,6 +294,8 @@ export const COMPONENTS = {
295
294
  UGroups: "ui.container-groups",
296
295
  UAccordion: "ui.container-accordion",
297
296
  UAccordionItem: "ui.container-accordion-item",
297
+ UEmpty: "ui.container-empty",
298
+ UPlaceholder: "ui.container-placeholder",
298
299
  UCard: "ui.container-card",
299
300
  UModal: "ui.container-modal",
300
301
  UModalConfirm: "ui.container-modal-confirm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.3.5-beta.1",
3
+ "version": "1.3.5-beta.2",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
@@ -57,7 +57,7 @@
57
57
  "@vue/eslint-config-typescript": "^14.6.0",
58
58
  "@vue/test-utils": "^2.4.6",
59
59
  "@vue/tsconfig": "^0.7.0",
60
- "@vueless/storybook": "^1.2.13",
60
+ "@vueless/storybook": "^1.3.1",
61
61
  "eslint": "^9.32.0",
62
62
  "eslint-plugin-storybook": "^10.0.2",
63
63
  "eslint-plugin-vue": "^10.3.0",
package/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import UTextDefaultConfig from "./ui.text-block/config";
2
2
  import UAlertDefaultConfig from "./ui.text-alert/config";
3
- import UEmptyDefaultConfig from "./ui.text-empty/config";
3
+ import UEmptyDefaultConfig from "./ui.container-empty/config";
4
4
  import UFileDefaultConfig from "./ui.text-file/config";
5
5
  import UFilesDefaultConfig from "./ui.text-files/config";
6
6
  import UHeaderDefaultConfig from "./ui.text-header/config";
@@ -12,6 +12,12 @@ export default /*tw*/ {
12
12
  block: {
13
13
  true: "w-full",
14
14
  },
15
+ grow: {
16
+ true: "flex-grow",
17
+ },
18
+ shrink: {
19
+ true: "flex-shrink",
20
+ },
15
21
  gap: {
16
22
  none: "gap-0",
17
23
  "3xs": "gap-0.5",
@@ -61,5 +67,7 @@ export default /*tw*/ {
61
67
  wrap: false,
62
68
  reverse: false,
63
69
  block: false,
70
+ grow: false,
71
+ shrink: false,
64
72
  },
65
73
  };
@@ -140,6 +140,32 @@ describe("UCol.vue", () => {
140
140
  expect(component.attributes("class")).toContain(expectedClasses);
141
141
  });
142
142
 
143
+ it("Grow – applies the correct grow class", () => {
144
+ const grow = true;
145
+ const expectedClasses = "flex-grow";
146
+
147
+ const component = mount(UCol, {
148
+ props: {
149
+ grow,
150
+ },
151
+ });
152
+
153
+ expect(component.attributes("class")).toContain(expectedClasses);
154
+ });
155
+
156
+ it("Shrink – applies the correct shrink class", () => {
157
+ const shrink = true;
158
+ const expectedClasses = "flex-shrink";
159
+
160
+ const component = mount(UCol, {
161
+ props: {
162
+ shrink,
163
+ },
164
+ });
165
+
166
+ expect(component.attributes("class")).toContain(expectedClasses);
167
+ });
168
+
143
169
  it("Tag – renders the correct HTML tag", () => {
144
170
  const tags = ["div", "section", "article", "main", "aside", "nav", "span"];
145
171
 
@@ -49,6 +49,16 @@ export interface Props {
49
49
  */
50
50
  block?: boolean;
51
51
 
52
+ /**
53
+ * Allow flex item to grow to fill available space (flex-grow).
54
+ */
55
+ grow?: boolean;
56
+
57
+ /**
58
+ * Allow flex item to shrink if necessary (flex-shrink).
59
+ */
60
+ shrink?: boolean;
61
+
52
62
  /**
53
63
  * Allows changing HTML tag.
54
64
  */
@@ -6,7 +6,7 @@ import {
6
6
  getDocsDescription,
7
7
  } from "../../utils/storybook";
8
8
 
9
- import UEmpty from "../../ui.text-empty/UEmpty.vue";
9
+ import UEmpty from "../../ui.container-empty/UEmpty.vue";
10
10
  import UButton from "../../ui.button/UButton.vue";
11
11
  import UIcon from "../../ui.image-icon/UIcon.vue";
12
12
  import URow from "../../ui.container-row/URow.vue";
@@ -25,8 +25,8 @@ interface UEmptyArgs extends Props {
25
25
  }
26
26
 
27
27
  export default {
28
- id: "4090",
29
- title: "Text & Content / Empty",
28
+ id: "5055",
29
+ title: "Containers / Empty",
30
30
  component: UEmpty,
31
31
  args: {
32
32
  title: "No contacts",
@@ -95,12 +95,15 @@ DefaultSlot.args = {
95
95
  `,
96
96
  };
97
97
 
98
+ export const PlaceholderIcon = DefaultTemplate.bind({});
99
+ PlaceholderIcon.args = { placeholderIcon: "inbox", title: "No messages" };
100
+
98
101
  export const FooterSlot = DefaultTemplate.bind({});
99
102
  FooterSlot.args = {
100
103
  slotTemplate: `
101
104
  <template #footer>
102
105
  <UButton
103
- label="Add new one"
106
+ label="Add"
104
107
  size="sm"
105
108
  color="grayscale"
106
109
  variant="soft"
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import { useTemplateRef } from "vue";
3
+
4
+ import { useUI } from "../composables/useUI";
5
+ import { getDefaults } from "../utils/ui";
6
+
7
+ import { COMPONENT_NAME } from "./constants";
8
+ import defaultConfig from "./config";
9
+
10
+ import type { Props, Config } from "./types";
11
+
12
+ defineOptions({ inheritAttrs: false });
13
+
14
+ withDefaults(defineProps<Props>(), {
15
+ ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
16
+ label: "",
17
+ });
18
+
19
+ const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
20
+
21
+ defineExpose({
22
+ /**
23
+ * A reference to the component's wrapper element for direct DOM manipulation.
24
+ * @property {HTMLDivElement}
25
+ */
26
+ wrapperRef,
27
+ });
28
+
29
+ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs } = useUI<Config>(defaultConfig);
30
+ </script>
31
+
32
+ <template>
33
+ <div
34
+ ref="wrapper"
35
+ role="region"
36
+ :aria-label="label || 'Placeholder area'"
37
+ v-bind="wrapperAttrs"
38
+ :data-test="getDataTest()"
39
+ >
40
+ <div v-bind="contentAttrs">
41
+ <!--
42
+ @slot Use it to add custom content inside the placeholder.
43
+ @binding {string} label
44
+ -->
45
+ <slot :label="label">
46
+ <span v-if="label" v-bind="labelAttrs">{{ label }}</span>
47
+ </slot>
48
+ </div>
49
+ </div>
50
+ </template>
@@ -0,0 +1,40 @@
1
+ export default /*tw*/ {
2
+ wrapper: {
3
+ base: `
4
+ flex items-center justify-center w-full h-full
5
+ border-solid border-2 border-{color}/15
6
+ `,
7
+ variants: {
8
+ rounded: {
9
+ sm: "rounded-small",
10
+ md: "rounded-medium",
11
+ lg: "rounded-large",
12
+ none: "rounded-none",
13
+ },
14
+ dashed: {
15
+ true: "border-dashed",
16
+ },
17
+ dotted: {
18
+ true: "border-dotted",
19
+ },
20
+ },
21
+ },
22
+ content: "flex items-center justify-center",
23
+ label: {
24
+ base: "text-{color} select-none",
25
+ variants: {
26
+ size: {
27
+ sm: "text-small",
28
+ md: "text-medium",
29
+ lg: "text-large",
30
+ },
31
+ },
32
+ },
33
+ defaults: {
34
+ color: "neutral",
35
+ size: "md",
36
+ rounded: "md",
37
+ dashed: false,
38
+ dotted: false,
39
+ },
40
+ };
@@ -0,0 +1,5 @@
1
+ /*
2
+ This const is needed to prevent the issue in script setup:
3
+ `defineProps` is referencing locally declared variables. (vue/valid-define-props)
4
+ */
5
+ export const COMPONENT_NAME = "UPlaceholder";
@@ -0,0 +1,15 @@
1
+ import { Meta, Title, Description, Primary, Controls, Stories } from "@storybook/addon-docs/blocks";
2
+ import * as PlaceholderStories from "./stories";
3
+
4
+ <Meta of={PlaceholderStories} />
5
+
6
+ <Title />
7
+
8
+ <Description />
9
+
10
+ <Primary />
11
+
12
+ <Controls />
13
+
14
+ <Stories />
15
+
@@ -0,0 +1,126 @@
1
+ import type { Meta, StoryFn } from "@storybook/vue3-vite";
2
+ import {
3
+ getArgs,
4
+ getArgTypes,
5
+ getSlotNames,
6
+ getSlotsFragment,
7
+ getDocsDescription,
8
+ } from "../../utils/storybook";
9
+
10
+ import UPlaceholder from "../UPlaceholder.vue";
11
+ import UCol from "../../ui.container-col/UCol.vue";
12
+ import URow from "../../ui.container-row/URow.vue";
13
+ import UText from "../../ui.text-block/UText.vue";
14
+ import UHeader from "../../ui.text-header/UHeader.vue";
15
+
16
+ import type { Props } from "../types";
17
+
18
+ interface PlaceholderArgs extends Props {
19
+ slotTemplate?: string;
20
+ enum?: string;
21
+ }
22
+
23
+ export default {
24
+ id: "5052",
25
+ title: "Containers / Placeholder",
26
+ args: {
27
+ label: "Placeholder label.",
28
+ dashed: true,
29
+ },
30
+ argTypes: {
31
+ ...getArgTypes(UPlaceholder.__name),
32
+ },
33
+ parameters: {
34
+ docs: {
35
+ ...getDocsDescription(UPlaceholder.__name),
36
+ },
37
+ },
38
+ } as Meta;
39
+
40
+ const DefaultTemplate: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs) => ({
41
+ components: { UPlaceholder, URow, UText },
42
+ setup: () => ({ args, slots: getSlotNames(UPlaceholder.__name) }),
43
+ template: `
44
+ <UPlaceholder v-bind="args" class="h-32">
45
+ ${args.slotTemplate || getSlotsFragment("")}
46
+ </UPlaceholder>
47
+ `,
48
+ });
49
+
50
+ const EnumTemplate: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs, { argTypes }) => ({
51
+ components: { UPlaceholder, URow },
52
+ setup: () => ({ args, argTypes, getArgs }),
53
+ template: `
54
+ <URow>
55
+ <UPlaceholder
56
+ v-for="option in argTypes?.[args.enum]?.options"
57
+ v-bind="getArgs(args, option)"
58
+ :key="option"
59
+ class="h-32"
60
+ />
61
+ </URow>
62
+ `,
63
+ });
64
+
65
+ export const Default = DefaultTemplate.bind({});
66
+ Default.args = {};
67
+
68
+ export const NoLabel = DefaultTemplate.bind({});
69
+ NoLabel.args = { label: "" };
70
+
71
+ export const Sizes = EnumTemplate.bind({});
72
+ Sizes.args = { enum: "size", label: "{enumValue}" };
73
+
74
+ export const Rounded = EnumTemplate.bind({});
75
+ Rounded.args = { enum: "rounded", label: "{enumValue}" };
76
+
77
+ export const BorderStyle: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs) => ({
78
+ components: { UPlaceholder, URow },
79
+ setup: () => ({ args }),
80
+ template: `
81
+ <URow>
82
+ <UPlaceholder label="Solid" class="h-32" />
83
+ <UPlaceholder label="Dashed" dashed class="h-32" />
84
+ <UPlaceholder label="Dotted" dotted class="h-32" />
85
+ </URow>
86
+ `,
87
+ });
88
+
89
+ export const Colors = EnumTemplate.bind({});
90
+ Colors.args = { enum: "color", label: "{enumValue}" };
91
+
92
+ export const WithSlot: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs) => ({
93
+ components: { UPlaceholder, UCol, UText, UHeader },
94
+ setup: () => ({ args }),
95
+ template: `
96
+ <UPlaceholder v-bind="args" class="h-32">
97
+ <UCol align="center" gap="2xs">
98
+ <UHeader size="lg">📦</UHeader>
99
+ <UText color="neutral">Custom slot content</UText>
100
+ </UCol>
101
+ </UPlaceholder>
102
+ `,
103
+ });
104
+
105
+ export const FixedSize: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs) => ({
106
+ components: { UPlaceholder },
107
+ setup: () => ({ args }),
108
+ template: `
109
+ <UPlaceholder label="Fixed size: 300x150" class="w-[300px] h-[150px]" />
110
+ `,
111
+ });
112
+
113
+ export const LayoutExample: StoryFn<PlaceholderArgs> = (args: PlaceholderArgs) => ({
114
+ components: { UPlaceholder, URow, UCol },
115
+ setup: () => ({ args }),
116
+ template: `
117
+ <URow align="stretch" class="h-96">
118
+ <UPlaceholder label="Sidebar" class="w-64" />
119
+ <UCol align="stretch" grow>
120
+ <UPlaceholder label="Header" class="h-16" />
121
+ <UPlaceholder label="Main Content" />
122
+ <UPlaceholder label="Footer" class="h-12" />
123
+ </UCol>
124
+ </URow>
125
+ `,
126
+ });
@@ -0,0 +1,173 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import UPlaceholder from "../UPlaceholder.vue";
5
+
6
+ import type { Props } from "../types";
7
+
8
+ describe("UPlaceholder.vue", () => {
9
+ describe("Props", () => {
10
+ it("Size – applies the correct size class", () => {
11
+ const sizeClasses = {
12
+ sm: "text-small",
13
+ md: "text-medium",
14
+ lg: "text-large",
15
+ };
16
+
17
+ Object.entries(sizeClasses).forEach(([size, classes]) => {
18
+ const component = mount(UPlaceholder, {
19
+ props: {
20
+ size: size as Props["size"],
21
+ label: "Test",
22
+ },
23
+ });
24
+
25
+ const labelElement = component.find("span");
26
+
27
+ expect(labelElement.attributes("class")).toContain(classes);
28
+ });
29
+ });
30
+
31
+ it("Rounded – applies the correct rounded class", () => {
32
+ const roundedClasses = {
33
+ sm: "rounded-small",
34
+ md: "rounded-medium",
35
+ lg: "rounded-large",
36
+ none: "rounded-none",
37
+ };
38
+
39
+ Object.entries(roundedClasses).forEach(([rounded, classes]) => {
40
+ const component = mount(UPlaceholder, {
41
+ props: {
42
+ rounded: rounded as Props["rounded"],
43
+ },
44
+ });
45
+
46
+ expect(component.attributes("class")).toContain(classes);
47
+ });
48
+ });
49
+
50
+ it("Dashed – applies the correct border class", () => {
51
+ const dashed = true;
52
+ const expectedClass = "border-dashed";
53
+
54
+ const component = mount(UPlaceholder, {
55
+ props: {
56
+ dashed,
57
+ },
58
+ });
59
+
60
+ expect(component.attributes("class")).toContain(expectedClass);
61
+ });
62
+
63
+ it("Dotted – applies the correct border class", () => {
64
+ const dotted = true;
65
+ const expectedClass = "border-dotted";
66
+
67
+ const component = mount(UPlaceholder, {
68
+ props: {
69
+ dotted,
70
+ },
71
+ });
72
+
73
+ expect(component.attributes("class")).toContain(expectedClass);
74
+ });
75
+
76
+ it("Border – applies solid border class by default", () => {
77
+ const expectedClass = "border-solid";
78
+
79
+ const component = mount(UPlaceholder, {
80
+ props: {
81
+ dashed: false,
82
+ dotted: false,
83
+ },
84
+ });
85
+
86
+ expect(component.attributes("class")).toContain(expectedClass);
87
+ });
88
+
89
+ it("Color – applies the correct color class", () => {
90
+ const color = "primary";
91
+
92
+ const component = mount(UPlaceholder, {
93
+ props: {
94
+ color,
95
+ },
96
+ });
97
+
98
+ expect(component.attributes("class")).toContain(`border-${color}`);
99
+ });
100
+
101
+ it("Label – renders the correct label text", () => {
102
+ const label = "Test Label";
103
+
104
+ const component = mount(UPlaceholder, {
105
+ props: {
106
+ label,
107
+ },
108
+ });
109
+
110
+ expect(component.text()).toContain(label);
111
+ });
112
+
113
+ it("Label – sets aria-label from label prop", () => {
114
+ const label = "Test Area";
115
+
116
+ const component = mount(UPlaceholder, {
117
+ props: {
118
+ label,
119
+ },
120
+ });
121
+
122
+ expect(component.attributes("aria-label")).toBe(label);
123
+ });
124
+
125
+ it("Label – sets default aria-label when no label provided", () => {
126
+ const component = mount(UPlaceholder);
127
+
128
+ expect(component.attributes("aria-label")).toBe("Placeholder area");
129
+ });
130
+
131
+ it("Role – applies the correct role attribute", () => {
132
+ const component = mount(UPlaceholder);
133
+
134
+ expect(component.attributes("role")).toBe("region");
135
+ });
136
+
137
+ it("Data Test – applies the correct data-test attribute", () => {
138
+ const dataTest = "custom-test-id";
139
+
140
+ const component = mount(UPlaceholder, {
141
+ props: {
142
+ dataTest,
143
+ },
144
+ });
145
+
146
+ expect(component.attributes("data-test")).toBe(dataTest);
147
+ });
148
+ });
149
+
150
+ describe("Slots", () => {
151
+ it("Default – renders content from default slot", () => {
152
+ const slotContent = "Custom Content";
153
+ const slotClass = "custom-content";
154
+
155
+ const component = mount(UPlaceholder, {
156
+ slots: {
157
+ default: `<div class='${slotClass}'>${slotContent}</div>`,
158
+ },
159
+ });
160
+
161
+ expect(component.find(`.${slotClass}`).exists()).toBe(true);
162
+ expect(component.text()).toContain(slotContent);
163
+ });
164
+ });
165
+
166
+ describe("Exposed refs", () => {
167
+ it("wrapperRef – exposes wrapperRef", () => {
168
+ const component = mount(UPlaceholder);
169
+
170
+ expect(component.vm.wrapperRef).toBeDefined();
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,56 @@
1
+ import defaultConfig from "./config";
2
+
3
+ import type { ComponentConfig } from "../types";
4
+
5
+ export type Config = typeof defaultConfig;
6
+
7
+ export interface Props {
8
+ /**
9
+ * Placeholder label text.
10
+ */
11
+ label?: string;
12
+
13
+ /**
14
+ * Label text size.
15
+ */
16
+ size?: "sm" | "md" | "lg";
17
+
18
+ /**
19
+ * Border radius size.
20
+ */
21
+ rounded?: "sm" | "md" | "lg" | "none";
22
+
23
+ /**
24
+ * Use dashed border style.
25
+ */
26
+ dashed?: boolean;
27
+
28
+ /**
29
+ * Use dotted border style.
30
+ */
31
+ dotted?: boolean;
32
+
33
+ /**
34
+ * Border color.
35
+ */
36
+ color?:
37
+ | "primary"
38
+ | "secondary"
39
+ | "error"
40
+ | "warning"
41
+ | "success"
42
+ | "info"
43
+ | "notice"
44
+ | "neutral"
45
+ | "grayscale";
46
+
47
+ /**
48
+ * Component config object.
49
+ */
50
+ config?: ComponentConfig<Config>;
51
+
52
+ /**
53
+ * Data-test attribute for automated testing.
54
+ */
55
+ dataTest?: string | null;
56
+ }
@@ -12,6 +12,12 @@ export default /*tw*/ {
12
12
  block: {
13
13
  true: "w-full",
14
14
  },
15
+ grow: {
16
+ true: "flex-grow",
17
+ },
18
+ shrink: {
19
+ true: "flex-shrink",
20
+ },
15
21
  gap: {
16
22
  none: "gap-0",
17
23
  "2xs": "gap-1",
@@ -59,5 +65,7 @@ export default /*tw*/ {
59
65
  wrap: false,
60
66
  block: false,
61
67
  reverse: false,
68
+ grow: false,
69
+ shrink: false,
62
70
  },
63
71
  };
@@ -138,6 +138,32 @@ describe("URow.vue", () => {
138
138
  expect(component.attributes("class")).toContain(expectedClasses);
139
139
  });
140
140
 
141
+ it("Grow – applies the correct grow class", () => {
142
+ const grow = true;
143
+ const expectedClasses = "flex-grow";
144
+
145
+ const component = mount(URow, {
146
+ props: {
147
+ grow,
148
+ },
149
+ });
150
+
151
+ expect(component.attributes("class")).toContain(expectedClasses);
152
+ });
153
+
154
+ it("Shrink – applies the correct shrink class", () => {
155
+ const shrink = true;
156
+ const expectedClasses = "flex-shrink";
157
+
158
+ const component = mount(URow, {
159
+ props: {
160
+ shrink,
161
+ },
162
+ });
163
+
164
+ expect(component.attributes("class")).toContain(expectedClasses);
165
+ });
166
+
141
167
  it("Tag – renders the correct HTML tag", () => {
142
168
  const tags = ["div", "section", "article", "main", "aside", "nav", "span"];
143
169
 
@@ -49,6 +49,16 @@ export interface Props {
49
49
  */
50
50
  block?: boolean;
51
51
 
52
+ /**
53
+ * Allow flex item to grow to fill available space (flex-grow).
54
+ */
55
+ grow?: boolean;
56
+
57
+ /**
58
+ * Allow flex item to shrink if necessary (flex-shrink).
59
+ */
60
+ shrink?: boolean;
61
+
52
62
  /**
53
63
  * Allows changing HTML tag.
54
64
  */
@@ -7,7 +7,7 @@ import { getDefaults } from "../utils/ui";
7
7
  import { hasSlotContent } from "../utils/helper";
8
8
 
9
9
  import UIcon from "../ui.image-icon/UIcon.vue";
10
- import UEmpty from "../ui.text-empty/UEmpty.vue";
10
+ import UEmpty from "../ui.container-empty/UEmpty.vue";
11
11
 
12
12
  import { COMPONENT_NAME } from "./constants";
13
13
  import defaultConfig from "./config";
@@ -2,7 +2,7 @@ import { mount, VueWrapper } from "@vue/test-utils";
2
2
  import { describe, it, expect } from "vitest";
3
3
 
4
4
  import UDataList from "../UDataList.vue";
5
- import UEmpty from "../../ui.text-empty/UEmpty.vue";
5
+ import UEmpty from "../../ui.container-empty/UEmpty.vue";
6
6
  import UIcon from "../../ui.image-icon/UIcon.vue";
7
7
  import draggable from "vuedraggable";
8
8
 
@@ -12,7 +12,7 @@ import {
12
12
  } from "vue";
13
13
  import { isEqual } from "lodash-es";
14
14
 
15
- import UEmpty from "../ui.text-empty/UEmpty.vue";
15
+ import UEmpty from "../ui.container-empty/UEmpty.vue";
16
16
  import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
17
17
  import ULoaderProgress from "../ui.loader-progress/ULoaderProgress.vue";
18
18
  import UTableRow from "./UTableRow.vue";
@@ -4,7 +4,7 @@ import { nextTick } from "vue";
4
4
 
5
5
  import UTable from "../UTable.vue";
6
6
  import UTableRow from "../UTableRow.vue";
7
- import UEmpty from "../../ui.text-empty/UEmpty.vue";
7
+ import UEmpty from "../../ui.container-empty/UEmpty.vue";
8
8
  import ULoaderProgress from "../../ui.loader-progress/ULoaderProgress.vue";
9
9
  import UDivider from "../../ui.container-divider/UDivider.vue";
10
10
  import {
File without changes
File without changes