vueless 1.2.3 → 1.2.5-beta.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/constants.d.ts CHANGED
@@ -177,6 +177,7 @@ export namespace COMPONENTS {
177
177
  let UGroup: string;
178
178
  let UGroups: string;
179
179
  let UAccordion: string;
180
+ let UAccordionItem: string;
180
181
  let UCard: string;
181
182
  let UModal: string;
182
183
  let UModalConfirm: string;
package/constants.js CHANGED
@@ -287,6 +287,7 @@ export const COMPONENTS = {
287
287
  UGroup: "ui.container-group",
288
288
  UGroups: "ui.container-groups",
289
289
  UAccordion: "ui.container-accordion",
290
+ UAccordionItem: "ui.container-accordion-item",
290
291
  UCard: "ui.container-card",
291
292
  UModal: "ui.container-modal",
292
293
  UModalConfirm: "ui.container-modal-confirm",
package/index.d.ts CHANGED
@@ -94,6 +94,7 @@ export { default as URow } from "./ui.container-row/URow.vue";
94
94
  export { default as UGroup } from "./ui.container-group/UGroup.vue";
95
95
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
96
96
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
97
+ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
97
98
  export { default as UCard } from "./ui.container-card/UCard.vue";
98
99
  export { default as UModal } from "./ui.container-modal/UModal.vue";
99
100
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
package/index.ts CHANGED
@@ -100,6 +100,7 @@ export { default as URow } from "./ui.container-row/URow.vue";
100
100
  export { default as UGroup } from "./ui.container-group/UGroup.vue";
101
101
  export { default as UGroups } from "./ui.container-groups/UGroups.vue";
102
102
  export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
103
+ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
103
104
  export { default as UCard } from "./ui.container-card/UCard.vue";
104
105
  export { default as UModal } from "./ui.container-modal/UModal.vue";
105
106
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.3",
3
+ "version": "1.2.5-beta.0",
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",
package/types.ts CHANGED
@@ -20,6 +20,7 @@ import UDropdownBadgeConfig from "./ui.dropdown-badge/config";
20
20
  import UDropdownButtonConfig from "./ui.dropdown-button/config";
21
21
  import UDropdownLinkConfig from "./ui.dropdown-link/config";
22
22
  import UAccordionConfig from "./ui.container-accordion/config";
23
+ import UAccordionItemConfig from "./ui.container-accordion-item/config";
23
24
  import UCardConfig from "./ui.container-card/config";
24
25
  import UColConfig from "./ui.container-col/config";
25
26
  import UDividerConfig from "./ui.container-divider/config";
@@ -252,6 +253,7 @@ export interface Components {
252
253
  UDropdownLink: Partial<typeof UDropdownLinkConfig>;
253
254
  UListbox: Partial<typeof UListboxConfig>;
254
255
  UAccordion: Partial<typeof UAccordionConfig>;
256
+ UAccordionItem: Partial<typeof UAccordionItemConfig>;
255
257
  UCard: Partial<typeof UCardConfig>;
256
258
  UCol: Partial<typeof UColConfig>;
257
259
  UDivider: Partial<typeof UDividerConfig>;
@@ -1,119 +1,114 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, useId, useSlots, useTemplateRef } from "vue";
2
+ import { computed, provide, useTemplateRef, ref, watch } from "vue";
3
3
 
4
4
  import useUI from "../composables/useUI";
5
5
  import { getDefaults } from "../utils/ui";
6
- import { hasSlotContent } from "../utils/helper";
7
6
 
8
- import UIcon from "../ui.image-icon/UIcon.vue";
9
- import UDivider from "../ui.container-divider/UDivider.vue";
7
+ import UAccordionItem from "../ui.container-accordion-item/UAccordionItem.vue";
10
8
 
11
9
  import { COMPONENT_NAME } from "./constants";
12
10
  import defaultConfig from "./config";
13
11
 
14
- import type { Props, Config } from "./types";
12
+ import type { Props, Config, SetAccordionSelectedItem } from "./types";
15
13
 
16
14
  defineOptions({ inheritAttrs: false });
17
15
 
18
16
  const props = withDefaults(defineProps<Props>(), {
19
17
  ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
18
+ modelValue: undefined,
19
+ options: () => [],
20
20
  });
21
21
 
22
22
  const emit = defineEmits([
23
23
  /**
24
- * Triggers when the accordion item is toggled.
25
- * @property {string} elementId
26
- * @property {boolean} isOpened
24
+ * Triggers when the value attribute changes.
25
+ * @property {string} value
27
26
  */
28
- "click",
27
+ "update:modelValue",
29
28
  ]);
30
29
 
31
- const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
32
- const contentRef = useTemplateRef<HTMLDivElement>("content");
30
+ const accordionRef = useTemplateRef<HTMLDivElement>("accordion");
33
31
 
34
- const isOpened = ref(false);
32
+ const internalValue = ref<string | string[] | null>(props.multiple ? [] : null);
35
33
 
36
- const slots = useSlots();
37
- const elementId = props.id || useId();
34
+ const isControlled = computed(() => props.modelValue !== undefined);
38
35
 
39
- const toggleIconName = computed(() => {
40
- if (typeof props.toggleIcon === "string") {
41
- return props.toggleIcon;
42
- }
36
+ const selectedItem = computed({
37
+ get: () => (isControlled.value ? props.modelValue! : internalValue.value),
38
+ set: (value) => {
39
+ if (!isControlled.value) internalValue.value = value;
43
40
 
44
- return props.toggleIcon ? config.value.defaults.toggleIcon : "";
41
+ emit("update:modelValue", value);
42
+ },
45
43
  });
46
44
 
47
- function onClickItem(event: MouseEvent) {
48
- if (contentRef.value && contentRef.value.contains(event.target as Node)) {
45
+ provide<SetAccordionSelectedItem>("setAccordionSelectedItem", (value, opened) => {
46
+ if (props.multiple) {
47
+ let current: string[] = [];
48
+
49
+ if (selectedItem.value) {
50
+ current = Array.isArray(selectedItem.value) ? [...selectedItem.value] : [selectedItem.value];
51
+ }
52
+
53
+ if (opened && !current.includes(value)) {
54
+ current.push(value);
55
+ } else {
56
+ const index = current.indexOf(value);
57
+
58
+ if (index !== -1) {
59
+ current.splice(index, 1);
60
+ }
61
+ }
62
+
63
+ selectedItem.value = current;
64
+
49
65
  return;
50
66
  }
51
67
 
52
- isOpened.value = !isOpened.value;
68
+ selectedItem.value = opened ? value : null;
69
+ });
70
+
71
+ provide("getAccordionSelectedItem", () => selectedItem.value ?? null);
72
+ provide("getAccordionSize", () => props.size);
73
+ provide("getAccordionDisabled", () => props.disabled);
53
74
 
54
- emit("click", elementId, isOpened.value);
55
- }
75
+ watch(
76
+ () => props.multiple,
77
+ (isMultiple) => {
78
+ if (!isControlled.value) {
79
+ internalValue.value = isMultiple ? [] : null;
80
+ }
81
+ },
82
+ );
56
83
 
57
84
  defineExpose({
58
85
  /**
59
86
  * A reference to the component's wrapper element for direct DOM manipulation.
60
87
  * @property {HTMLDivElement}
61
88
  */
62
- wrapperRef,
89
+ accordionRef,
63
90
  });
64
91
 
65
- const mutatedProps = computed(() => ({
66
- /* component state, not a props */
67
- opened: isOpened.value,
68
- }));
69
-
70
- const {
71
- getDataTest,
72
- config,
73
- wrapperAttrs,
74
- descriptionAttrs,
75
- bodyAttrs,
76
- titleAttrs,
77
- contentAttrs,
78
- toggleIconAttrs,
79
- accordionDividerAttrs,
80
- } = useUI<Config>(defaultConfig, mutatedProps);
92
+ const { getDataTest, accordionItemAttrs, accordionAttrs } = useUI<Config>(defaultConfig);
81
93
  </script>
82
94
 
83
95
  <template>
84
- <div ref="wrapper" v-bind="wrapperAttrs" :data-test="getDataTest()" @click="onClickItem">
85
- <div v-bind="bodyAttrs">
86
- <div v-bind="titleAttrs">
87
- {{ title }}
88
- <!--
89
- @slot Use it to add something instead of the toggle icon.
90
- @binding {string} icon-name
91
- @binding {boolean} opened
92
- -->
93
- <slot name="toggle" :icon-name="toggleIconName" :opened="isOpened">
94
- <UIcon
95
- v-if="toggleIconName"
96
- :name="toggleIconName"
97
- :size="size"
98
- color="neutral"
99
- v-bind="toggleIconAttrs"
100
- />
101
- </slot>
102
- </div>
103
-
104
- <div
105
- v-if="description"
106
- :id="`description-${elementId}`"
107
- v-bind="descriptionAttrs"
108
- v-text="description"
96
+ <div ref="accordion" v-bind="accordionAttrs" :data-test="getDataTest()">
97
+ <!-- @slot Use it to add UAccordionItem directly. -->
98
+ <slot>
99
+ <UAccordionItem
100
+ v-for="(option, index) in options"
101
+ :key="index"
102
+ :model-value="selectedItem"
103
+ :value="option.value"
104
+ :opened="option.opened"
105
+ :title="option.title"
106
+ :description="option.description"
107
+ :size="size"
108
+ :disabled="disabled"
109
+ v-bind="accordionItemAttrs"
110
+ :data-test="getDataTest(`item-${index}`)"
109
111
  />
110
-
111
- <div v-if="isOpened && hasSlotContent(slots['default'])" ref="content" v-bind="contentAttrs">
112
- <!-- @slot Use it to add accordion content. -->
113
- <slot />
114
- </div>
115
- </div>
116
-
117
- <UDivider v-bind="accordionDividerAttrs" />
112
+ </slot>
118
113
  </div>
119
114
  </template>
@@ -1,51 +1,9 @@
1
1
  export default /*tw*/ {
2
- wrapper: "group cursor-pointer",
3
- body: "",
4
- title: {
5
- base: "flex items-center justify-between font-medium",
6
- variants: {
7
- size: {
8
- sm: "text-small",
9
- md: "text-medium",
10
- lg: "text-large",
11
- },
12
- },
13
- },
14
- description: {
15
- base: "text-accented h-0 opacity-0 transition-all",
16
- variants: {
17
- size: {
18
- sm: "text-tiny",
19
- md: "text-small",
20
- lg: "text-medium",
21
- },
22
- opened: {
23
- true: "pt-2 h-fit opacity-100",
24
- },
25
- },
26
- },
27
- content: "pt-3 cursor-default",
28
- toggleIcon: {
29
- base: "{UIcon} transition duration-300",
30
- variants: {
31
- opened: {
32
- true: "group-[*]:rotate-180",
33
- },
34
- },
35
- },
36
- accordionDivider: {
37
- base: "{UDivider} group-last:hidden",
38
- variants: {
39
- size: {
40
- sm: "py-4",
41
- md: "py-5",
42
- lg: "py-6",
43
- },
44
- },
45
- },
2
+ accordion: "flex flex-col w-full divide-y divide-muted",
3
+ accordionItem: "{UAccordionItem} py-5 first:pt-0 last:pb-0",
46
4
  defaults: {
47
5
  size: "md",
48
- /* icons */
49
- toggleIcon: "keyboard_arrow_down",
6
+ disabled: false,
7
+ multiple: false,
50
8
  },
51
9
  };
@@ -20,18 +20,41 @@ import type { Props } from "../types";
20
20
  interface UAccordionArgs extends Props {
21
21
  slotTemplate?: string;
22
22
  enum: "size";
23
+ class?: string;
23
24
  }
24
25
 
25
26
  export default {
26
- id: "5050",
27
+ id: "5040",
27
28
  title: "Containers / Accordion",
28
29
  component: UAccordion,
29
30
  args: {
30
- title: "Committed to Quality and Performance",
31
- description: trimText(
32
- `We take pride in delivering high-quality solutions tailored to your needs.
33
- Our expertise ensures that your project is built with efficiency, scalability, and reliability in mind.`,
34
- ),
31
+ modelValue: "2",
32
+ options: [
33
+ {
34
+ value: "1",
35
+ title: "Committed to Quality and Performance",
36
+ description:
37
+ trimText(`We take pride in delivering high-quality solutions tailored to your needs.
38
+ Our expertise ensures that your project is built with efficiency, scalability, and reliability in mind.
39
+ `),
40
+ },
41
+ {
42
+ value: "2",
43
+ title: "Pioneering Cutting-Edge Solutions",
44
+ description:
45
+ trimText(`Our team stays ahead of the curve, integrating the latest technologies
46
+ and best practices to drive innovation and create future-ready applications for your business.
47
+ `),
48
+ },
49
+ {
50
+ value: "3",
51
+ title: "Building Together for Long-Term Success",
52
+ description:
53
+ trimText(`We work closely with you to understand your goals, ensuring seamless communication
54
+ and a collaborative approach that leads to sustainable, impactful results.
55
+ `),
56
+ },
57
+ ],
35
58
  },
36
59
  argTypes: {
37
60
  ...getArgTypes(UAccordion.__name),
@@ -47,104 +70,42 @@ const DefaultTemplate: StoryFn<UAccordionArgs> = (args: UAccordionArgs) => ({
47
70
  components: { UAccordion, ULink, UButton, UCol, URow, UIcon },
48
71
  setup: () => ({ args, slots: getSlotNames(UAccordion.__name) }),
49
72
  template: `
50
- <UAccordion v-bind="args">
51
- ${args.slotTemplate || getSlotsFragment("")}
52
- </UAccordion>
53
- `,
54
- });
55
-
56
- const AccordionsTemplate: StoryFn<UAccordionArgs> = (args: UAccordionArgs) => ({
57
- components: { UAccordion, UButton },
58
- setup() {
59
- const slots = getSlotNames(UAccordion.__name);
60
-
61
- const accordions = [
62
- {
63
- title: "Committed to Quality and Performance",
64
- description: `
65
- We take pride in delivering high-quality solutions tailored to your needs.
66
- Our expertise ensures that your project is built with efficiency, scalability, and reliability in mind.
67
- `,
68
- },
69
- {
70
- title: "Pioneering Cutting-Edge Solutions",
71
- description: `
72
- Our team stays ahead of the curve, integrating the latest technologies and best practices
73
- to drive innovation and create future-ready applications for your business.
74
- `,
75
- },
76
- {
77
- title: "Building Together for Long-Term Success",
78
- description: `
79
- We work closely with you to understand your goals, ensuring seamless communication
80
- and a collaborative approach that leads to sustainable, impactful results.
81
- `,
82
- },
83
- ];
84
-
85
- return { args, slots, accordions };
86
- },
87
- template: `
88
- <UAccordion
89
- v-for="(accordion, index) in accordions"
90
- :key="index"
91
- v-bind="accordion"
92
- >
73
+ <UAccordion v-bind="args" v-model="args.modelValue">
93
74
  ${args.slotTemplate || getSlotsFragment("")}
94
75
  </UAccordion>
95
76
  `,
96
77
  });
97
78
 
98
79
  const EnumTemplate: StoryFn<UAccordionArgs> = (args: UAccordionArgs, { argTypes }) => ({
99
- components: { UAccordion },
80
+ components: { UAccordion, UCol },
100
81
  setup: () => ({ args, argTypes, getArgs }),
101
82
  template: `
102
- <UAccordion
103
- v-for="option in argTypes?.[args.enum]?.options"
104
- v-bind="getArgs(args, option)"
105
- :key="option"
106
- />
83
+ <UCol gap="xl" block>
84
+ <UAccordion
85
+ v-for="option in argTypes?.[args.enum]?.options"
86
+ v-bind="getArgs(args, option)"
87
+ v-model="args.modelValue"
88
+ :key="option"
89
+ />
90
+ </UCol>
107
91
  `,
108
92
  });
109
93
 
110
94
  export const Default = DefaultTemplate.bind({});
111
95
  Default.args = {};
112
96
 
113
- export const Accordions = AccordionsTemplate.bind({});
114
- Accordions.args = {};
97
+ export const Disabled = DefaultTemplate.bind({});
98
+ Disabled.args = { disabled: true };
115
99
 
116
100
  export const Sizes = EnumTemplate.bind({});
117
- Sizes.args = { enum: "size", description: "{enumValue}" };
118
-
119
- export const DefaultSlot = DefaultTemplate.bind({});
120
- DefaultSlot.args = {
121
- slotTemplate: `
122
- <template #default>
123
- <UCol gap="sm">
124
- <URow gap="xs" align="end">
125
- <UIcon name="contact_mail" size="xs" color="primary" />
126
- <ULink label="Email services" />
127
- </URow>
128
-
129
- <URow gap="xs" align="end">
130
- <UIcon name="vpn_key" size="xs" color="primary" />
131
- <ULink label="VPN" />
132
- </URow>
133
-
134
- <URow gap="xs" align="end">
135
- <UIcon name="web_traffic" size="xs" color="primary" />
136
- <ULink label="SEO Tools" />
137
- </URow>
138
- </UCol>
139
- </template>
140
- `,
141
- };
142
-
143
- export const ToggleSlot = DefaultTemplate.bind({});
144
- ToggleSlot.args = {
145
- slotTemplate: `
146
- <template #toggle="{ opened }">
147
- <ULink :label="opened ? 'Collapse' : 'Expand'" color="grayscale" />
148
- </template>
149
- `,
101
+ Sizes.args = {
102
+ enum: "size",
103
+ class: "w-full",
104
+ options: [
105
+ {
106
+ value: "1",
107
+ title: "Committed to Quality and Performance",
108
+ description: "{enumValue}",
109
+ },
110
+ ],
150
111
  };