vueless 1.3.7-beta.8 → 1.3.7

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/components.d.ts CHANGED
@@ -37,6 +37,7 @@ 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
39
  export { default as UBadge } from "./ui.text-badge/UBadge.vue";
40
+ export { default as UKey } from "./ui.text-key/UKey.vue";
40
41
  /* Containers */
41
42
  export { default as UDivider } from "./ui.container-divider/UDivider.vue";
42
43
  export { default as UCol } from "./ui.container-col/UCol.vue";
package/components.ts CHANGED
@@ -37,6 +37,7 @@ 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
39
  export { default as UBadge } from "./ui.text-badge/UBadge.vue";
40
+ export { default as UKey } from "./ui.text-key/UKey.vue";
40
41
  /* Containers */
41
42
  export { default as UDivider } from "./ui.container-divider/UDivider.vue";
42
43
  export { default as UCol } from "./ui.container-col/UCol.vue";
package/constants.d.ts CHANGED
@@ -178,6 +178,7 @@ export namespace COMPONENTS {
178
178
  let UFile: string;
179
179
  let UFiles: string;
180
180
  let UBadge: string;
181
+ let UKey: string;
181
182
  let UDivider: string;
182
183
  let UCol: string;
183
184
  let URow: string;
package/constants.js CHANGED
@@ -286,6 +286,7 @@ export const COMPONENTS = {
286
286
  UFile: "ui.text-file",
287
287
  UFiles: "ui.text-files",
288
288
  UBadge: "ui.text-badge",
289
+ UKey: "ui.text-key",
289
290
 
290
291
  /* Containers */
291
292
  UDivider: "ui.container-divider",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.3.7-beta.8",
3
+ "version": "1.3.7",
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",
@@ -0,0 +1,71 @@
1
+ <script setup lang="ts">
2
+ import { useTemplateRef, computed } from "vue";
3
+
4
+ import { useUI } from "../composables/useUI";
5
+ import { getDefaults } from "../utils/ui";
6
+
7
+ import { COMPONENT_NAME, KEY_SYMBOLS } from "./constants";
8
+ import defaultConfig from "./config";
9
+
10
+ import type { Props, Config } from "./types";
11
+
12
+ defineOptions({ inheritAttrs: false });
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
16
+ });
17
+
18
+ const keyRef = useTemplateRef<HTMLElement>("key");
19
+
20
+ defineExpose({
21
+ /**
22
+ * A reference to the key element for direct DOM manipulation.
23
+ * @property {HTMLElement}
24
+ */
25
+ keyRef,
26
+ });
27
+
28
+ const keys = computed(() => {
29
+ if (!props.value) return [];
30
+
31
+ if (props.value.includes(" + ")) {
32
+ return props.value.split(" + ").map((key) => key.trim());
33
+ }
34
+
35
+ return [props.value];
36
+ });
37
+
38
+ const getDisplayValue = (key: string) => {
39
+ const lowerValue = key.toLowerCase();
40
+
41
+ return KEY_SYMBOLS[lowerValue as keyof typeof KEY_SYMBOLS] || key;
42
+ };
43
+
44
+ const displayValue = computed(() => {
45
+ if (keys.value.length === 0) return "";
46
+ if (keys.value.length === 1) return getDisplayValue(keys.value[0]);
47
+
48
+ return keys.value.map(getDisplayValue).join(" + ");
49
+ });
50
+
51
+ const { getDataTest, keyAttrs } = useUI<Config>(defaultConfig);
52
+ </script>
53
+
54
+ <template>
55
+ <kbd ref="key" v-bind="keyAttrs" :data-test="getDataTest()">
56
+ <!--
57
+ @slot Use it to add custom content.
58
+ @binding {string} value
59
+ -->
60
+ <slot :value="displayValue">
61
+ <template v-if="keys.length > 1">
62
+ <template v-for="(key, index) in keys" :key="index">
63
+ {{ getDisplayValue(key) }}<span v-if="index < keys.length - 1">+</span>
64
+ </template>
65
+ </template>
66
+ <template v-else>
67
+ {{ displayValue }}
68
+ </template>
69
+ </slot>
70
+ </kbd>
71
+ </template>
@@ -0,0 +1,26 @@
1
+ export default /*tw*/ {
2
+ key: {
3
+ base: `
4
+ inline-flex items-center justify-center gap-1 border border-solid
5
+ rounded-small font-medium font-mono !leading-none outline-hidden
6
+ `,
7
+ variants: {
8
+ variant: {
9
+ solid: "border-transparent text-inverted bg-{color}",
10
+ outlined: "border-{color} text-{color}",
11
+ subtle: "border-{color}/15 text-{color} bg-{color}/10",
12
+ soft: "border-transparent text-{color} bg-{color}/10",
13
+ },
14
+ size: {
15
+ sm: "px-1 min-w-5 h-5 text-tiny",
16
+ md: "px-1.5 min-w-6 h-6 text-tiny",
17
+ lg: "px-1.5 min-w-7 h-7 text-small",
18
+ },
19
+ },
20
+ },
21
+ defaults: {
22
+ color: "grayscale",
23
+ variant: "outlined",
24
+ size: "md",
25
+ },
26
+ };
@@ -0,0 +1,30 @@
1
+ export const COMPONENT_NAME = "UKey";
2
+
3
+ export const KEY_SYMBOLS = {
4
+ command: "⌘",
5
+ cmd: "⌘",
6
+ meta: "⌘",
7
+ shift: "⇧",
8
+ alt: "⌥",
9
+ option: "⌥",
10
+ ctrl: "⌃",
11
+ control: "⌃",
12
+ enter: "↵",
13
+ return: "↵",
14
+ delete: "⌫",
15
+ backspace: "⌫",
16
+ escape: "⎋",
17
+ esc: "⎋",
18
+ tab: "⇥",
19
+ capslock: "⇪",
20
+ caps: "⇪",
21
+ up: "↑",
22
+ down: "↓",
23
+ left: "←",
24
+ right: "→",
25
+ pageup: "⇞",
26
+ pagedown: "⇟",
27
+ home: "↖",
28
+ end: "↘",
29
+ space: "␣",
30
+ } as const;
@@ -0,0 +1,154 @@
1
+ import {
2
+ getArgs,
3
+ getArgTypes,
4
+ getSlotNames,
5
+ getSlotsFragment,
6
+ getDocsDescription,
7
+ } from "../../utils/storybook";
8
+
9
+ import UKey from "../../ui.text-key/UKey.vue";
10
+ import URow from "../../ui.container-row/URow.vue";
11
+ import UCol from "../../ui.container-col/UCol.vue";
12
+
13
+ import type { Meta, StoryFn } from "@storybook/vue3-vite";
14
+ import type { Props } from "../types";
15
+
16
+ interface UKeyArgs extends Props {
17
+ slotTemplate?: string;
18
+ enum: "variant" | "size" | "color";
19
+ outerEnum: "variant";
20
+ }
21
+
22
+ export default {
23
+ id: "4096",
24
+ title: "Text & Content / Key",
25
+ component: UKey,
26
+ args: {
27
+ value: "⌘",
28
+ },
29
+ argTypes: {
30
+ ...getArgTypes(UKey.__name),
31
+ },
32
+ parameters: {
33
+ docs: {
34
+ ...getDocsDescription(UKey.__name),
35
+ },
36
+ },
37
+ } as Meta;
38
+
39
+ const DefaultTemplate: StoryFn<UKeyArgs> = (args: UKeyArgs) => ({
40
+ components: { UKey },
41
+ setup: () => ({ args, slots: getSlotNames(UKey.__name) }),
42
+ template: `
43
+ <UKey v-bind="args">
44
+ ${args.slotTemplate || getSlotsFragment("")}
45
+ </UKey>
46
+ `,
47
+ });
48
+
49
+ const EnumTemplate: StoryFn<UKeyArgs> = (args: UKeyArgs, { argTypes }) => ({
50
+ components: { UKey, URow },
51
+ setup: () => ({ args, argTypes, getArgs }),
52
+ template: `
53
+ <URow>
54
+ <UKey
55
+ v-for="option in argTypes?.[args.enum]?.options"
56
+ v-bind="getArgs(args, option)"
57
+ :key="option"
58
+ />
59
+ </URow>
60
+ `,
61
+ });
62
+
63
+ const MultiEnumTemplate: StoryFn<UKeyArgs> = (args: UKeyArgs, { argTypes }) => ({
64
+ components: { UKey, URow, UCol },
65
+ setup: () => ({ args, argTypes, getArgs }),
66
+ template: `
67
+ <UCol>
68
+ <URow v-for="outerOption in argTypes?.[args.outerEnum]?.options" :key="outerOption">
69
+ <UKey
70
+ v-for="option in argTypes?.[args.enum]?.options"
71
+ v-bind="getArgs(args, option, outerOption)"
72
+ :key="option"
73
+ />
74
+ </URow>
75
+ </UCol>
76
+ `,
77
+ });
78
+
79
+ export const Default = DefaultTemplate.bind({});
80
+ Default.args = {};
81
+
82
+ export const Variants = EnumTemplate.bind({});
83
+ Variants.args = { enum: "variant" };
84
+
85
+ export const Sizes = EnumTemplate.bind({});
86
+ Sizes.args = { enum: "size" };
87
+
88
+ export const Colors = MultiEnumTemplate.bind({});
89
+ Colors.args = { outerEnum: "variant", enum: "color", value: "K" };
90
+
91
+ export const SystemKeys: StoryFn<UKeyArgs> = (args) => ({
92
+ components: { UKey, URow, UCol },
93
+ setup() {
94
+ return { args };
95
+ },
96
+ template: `
97
+ <UCol>
98
+ <URow>
99
+ <UKey value="command" />
100
+ <UKey value="shift" />
101
+ <UKey value="alt" />
102
+ <UKey value="ctrl" />
103
+ </URow>
104
+ <URow>
105
+ <UKey value="enter" />
106
+ <UKey value="delete" />
107
+ <UKey value="escape" />
108
+ <UKey value="tab" />
109
+ </URow>
110
+ <URow>
111
+ <UKey value="up" />
112
+ <UKey value="down" />
113
+ <UKey value="left" />
114
+ <UKey value="right" />
115
+ </URow>
116
+ <URow>
117
+ <UKey value="pageup" />
118
+ <UKey value="pagedown" />
119
+ <UKey value="home" />
120
+ <UKey value="end" />
121
+ </URow>
122
+ </UCol>
123
+ `,
124
+ });
125
+
126
+ export const Shortcuts: StoryFn<UKeyArgs> = (args) => ({
127
+ components: { UKey, URow, UCol },
128
+ setup() {
129
+ return { args };
130
+ },
131
+ template: `
132
+ <UCol>
133
+ <UKey value="command + K" />
134
+ <UKey value="ctrl + shift + P" />
135
+ <UKey value="alt + F4" />
136
+ </UCol>
137
+ `,
138
+ });
139
+
140
+ export const Slots: StoryFn<UKeyArgs> = (args) => ({
141
+ components: { UKey, URow },
142
+ setup() {
143
+ return { args };
144
+ },
145
+ template: `
146
+ <URow>
147
+ <UKey value="command + K">
148
+ <template #default="{ value }">
149
+ Shortcut: {{ value }}
150
+ </template>
151
+ </UKey>
152
+ </URow>
153
+ `,
154
+ });
@@ -0,0 +1,148 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import UKey from "../UKey.vue";
5
+
6
+ import type { Props } from "../types";
7
+ import type { ComponentPublicInstance } from "vue";
8
+
9
+ describe("UKey.vue", () => {
10
+ describe("Props", () => {
11
+ it("Value – displays the correct value", () => {
12
+ const value = "K";
13
+
14
+ const component = mount(UKey, {
15
+ props: {
16
+ value,
17
+ },
18
+ });
19
+
20
+ expect(component.text()).toBe(value);
21
+ });
22
+
23
+ it("Value – displays system key symbols", () => {
24
+ const systemKeys = {
25
+ command: "⌘",
26
+ shift: "⇧",
27
+ alt: "⌥",
28
+ ctrl: "⌃",
29
+ enter: "↵",
30
+ escape: "⎋",
31
+ tab: "⇥",
32
+ };
33
+
34
+ Object.entries(systemKeys).forEach(([key, symbol]) => {
35
+ const component = mount(UKey, {
36
+ props: {
37
+ value: key,
38
+ },
39
+ });
40
+
41
+ expect(component.text()).toBe(symbol);
42
+ });
43
+ });
44
+
45
+ it("Size – applies the correct size class", () => {
46
+ const sizes = {
47
+ sm: "h-5",
48
+ md: "h-6",
49
+ lg: "h-7",
50
+ };
51
+
52
+ Object.entries(sizes).forEach(([size, classes]) => {
53
+ const component = mount(UKey, {
54
+ props: {
55
+ size: size as Props["size"],
56
+ value: "K",
57
+ },
58
+ });
59
+
60
+ expect(component.attributes("class")).toContain(classes);
61
+ });
62
+ });
63
+
64
+ it("Variant – applies the correct variant class", () => {
65
+ const variants = ["solid", "outlined", "subtle", "soft"];
66
+
67
+ variants.forEach((variant) => {
68
+ const component = mount(UKey, {
69
+ props: {
70
+ variant: variant as Props["variant"],
71
+ value: "K",
72
+ },
73
+ });
74
+
75
+ expect(component.attributes("class")).toBeDefined();
76
+ });
77
+ });
78
+
79
+ it("Color – applies the correct color class", () => {
80
+ const colors = [
81
+ "primary",
82
+ "secondary",
83
+ "error",
84
+ "warning",
85
+ "success",
86
+ "info",
87
+ "notice",
88
+ "neutral",
89
+ "grayscale",
90
+ ];
91
+
92
+ colors.forEach((color) => {
93
+ const component = mount(UKey, {
94
+ props: {
95
+ color: color as Props["color"],
96
+ value: "K",
97
+ },
98
+ });
99
+
100
+ expect(component.attributes("class")).toContain(color);
101
+ });
102
+ });
103
+
104
+ it("DataTest – applies the correct data-test attribute", () => {
105
+ const dataTest = "test-key";
106
+
107
+ const component = mount(UKey, {
108
+ props: {
109
+ dataTest,
110
+ value: "K",
111
+ },
112
+ });
113
+
114
+ expect(component.attributes("data-test")).toBe(dataTest);
115
+ });
116
+ });
117
+
118
+ describe("Slots", () => {
119
+ it("Default – renders content from default slot", () => {
120
+ const slotContent = "Custom Key";
121
+
122
+ const component = mount(UKey, {
123
+ props: {
124
+ value: "K",
125
+ },
126
+ slots: {
127
+ default: slotContent,
128
+ },
129
+ });
130
+
131
+ expect(component.text()).toBe(slotContent);
132
+ });
133
+ });
134
+
135
+ describe("Exposed refs", () => {
136
+ it("keyRef – exposes keyRef", () => {
137
+ const component = mount(UKey, {
138
+ props: {
139
+ value: "K",
140
+ },
141
+ });
142
+
143
+ expect(
144
+ (component.vm as ComponentPublicInstance & { keyRef: HTMLElement }).keyRef,
145
+ ).toBeDefined();
146
+ });
147
+ });
148
+ });
@@ -0,0 +1,45 @@
1
+ import defaultConfig from "./config";
2
+ import type { ComponentConfig } from "../types";
3
+
4
+ export type Config = typeof defaultConfig;
5
+
6
+ export interface Props {
7
+ /**
8
+ * Key value to display.
9
+ */
10
+ value?: string;
11
+
12
+ /**
13
+ * Kbd variant.
14
+ */
15
+ variant?: "solid" | "outlined" | "subtle" | "soft";
16
+
17
+ /**
18
+ * Kbd size.
19
+ */
20
+ size?: "sm" | "md" | "lg";
21
+
22
+ /**
23
+ * Kbd color.
24
+ */
25
+ color?:
26
+ | "primary"
27
+ | "secondary"
28
+ | "error"
29
+ | "warning"
30
+ | "success"
31
+ | "info"
32
+ | "notice"
33
+ | "neutral"
34
+ | "grayscale";
35
+
36
+ /**
37
+ * Component config object.
38
+ */
39
+ config?: ComponentConfig<Config>;
40
+
41
+ /**
42
+ * Data-test attribute for automated testing.
43
+ */
44
+ dataTest?: string | null;
45
+ }