vueless 0.0.668 → 0.0.670

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.
@@ -123,24 +123,6 @@ export default function useUI<T>(
123
123
  const vuelessAttrs = ref({});
124
124
 
125
125
  const attrs = useAttrs() as KeyAttrs;
126
- const isDev = isCSR && import.meta.env?.DEV;
127
- const isTopLevelKey = (topLevelClassKey || firstClassKey) === configKey;
128
-
129
- const extendsKeyConfig = getExtendsKeyConfig(configKey);
130
- const extendsKeyNestedComponent = getNestedComponent(extendsKeyConfig);
131
- const keyNestedComponent = getNestedComponent(config.value[configKey]);
132
- const nestedComponent = extendsKeyNestedComponent || keyNestedComponent || componentName;
133
-
134
- const commonAttrs: KeyAttrs = {
135
- ...(isTopLevelKey ? attrs : {}),
136
- "vl-component": isDev ? attrs["vl-component"] || componentName || null : null,
137
- "vl-key": isDev ? attrs["vl-key"] || configKey || null : null,
138
- "vl-child-component": isDev && attrs["vl-component"] ? nestedComponent : null,
139
- "vl-child-key": isDev && attrs["vl-component"] ? configKey : null,
140
- };
141
-
142
- /* Delete value key to prevent v-model overwrite. */
143
- delete commonAttrs.value;
144
126
 
145
127
  const reactiveProps = computed(() => ({ ...props }));
146
128
 
@@ -156,8 +138,25 @@ export default function useUI<T>(
156
138
  keyConfig = config.value[configKey] as NestedComponent;
157
139
  }
158
140
 
141
+ const isDev = isCSR && import.meta.env?.DEV;
142
+ const isTopLevelKey = (topLevelClassKey || firstClassKey) === configKey;
143
+
159
144
  const extendsClasses = getExtendsClasses(configKey);
160
145
  const extendsKeyConfig = getExtendsKeyConfig(configKey);
146
+ const extendsKeyNestedComponent = getNestedComponent(extendsKeyConfig);
147
+ const keyNestedComponent = getNestedComponent(config.value[configKey]);
148
+ const nestedComponent = extendsKeyNestedComponent || keyNestedComponent || componentName;
149
+
150
+ const commonAttrs: KeyAttrs = {
151
+ ...(isTopLevelKey ? attrs : {}),
152
+ "vl-component": isDev ? attrs["vl-component"] || componentName || null : null,
153
+ "vl-key": isDev ? attrs["vl-key"] || configKey || null : null,
154
+ "vl-child-component": isDev && attrs["vl-component"] ? nestedComponent : null,
155
+ "vl-child-key": isDev && attrs["vl-component"] ? configKey : null,
156
+ };
157
+
158
+ /* Delete value key to prevent v-model overwrite. */
159
+ delete commonAttrs.value;
161
160
 
162
161
  vuelessAttrs.value = {
163
162
  ...commonAttrs,
@@ -165,6 +164,7 @@ export default function useUI<T>(
165
164
  config: getMergedConfig({
166
165
  defaultConfig: extendsKeyConfig,
167
166
  globalConfig: keyConfig,
167
+ propsConfig: attrs["config"] || {},
168
168
  }),
169
169
  ...getDefaults({
170
170
  ...(extendsKeyConfig.defaults || {}),
package/constants.js CHANGED
@@ -108,7 +108,6 @@ export const COMPONENTS = {
108
108
  UDatePicker: "ui.form-date-picker",
109
109
  UDatePickerRange: "ui.form-date-picker-range",
110
110
  ULabel: "ui.form-label",
111
- UColorPicker: "ui.form-color-picker",
112
111
 
113
112
  /* Text & Content */
114
113
  UHeader: "ui.text-header",
@@ -154,6 +153,7 @@ export const COMPONENTS = {
154
153
 
155
154
  /* Other */
156
155
  UDot: "ui.other-dot",
156
+ UThemeColorToggle: "ui.other-theme-color-toggle",
157
157
  };
158
158
 
159
159
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.668",
3
+ "version": "0.0.670",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
6
6
  "keywords": [
package/types.ts CHANGED
@@ -46,7 +46,7 @@ import URadioGroupConfig from "./ui.form-radio-group/config.ts";
46
46
  import USwitchConfig from "./ui.form-switch/config.ts";
47
47
  import UTextareaConfig from "./ui.form-textarea/config.ts";
48
48
  import ULabelConfig from "./ui.form-label/config.ts";
49
- import UColorPickerConfig from "./ui.form-color-picker/config.ts";
49
+ import UColorPickerConfig from "./ui.other-theme-color-toggle/config.ts";
50
50
  import UInputConfig from "./ui.form-input/config.ts";
51
51
  import UInputNumberConfig from "./ui.form-input-number/config.ts";
52
52
  import UInputRatingConfig from "./ui.form-input-rating/config.ts";
@@ -159,8 +159,7 @@ export type UnknownType = string | number | boolean | UnknownObject | undefined
159
159
  export type ComponentNames = keyof Components & string; // keys union
160
160
  export type Strategies = "merge" | "replace" | "override";
161
161
 
162
- export type Gray = "gray";
163
- export type GrayColors = "slate" | "cool" | "zinc" | "neutral" | "stone";
162
+ export type GrayColors = "slate" | "cool" | "zinc" | "neutral" | "stone" | string;
164
163
  export type BrandColors =
165
164
  | "grayscale"
166
165
  | "red"
@@ -179,7 +178,8 @@ export type BrandColors =
179
178
  | "purple"
180
179
  | "fuchsia"
181
180
  | "pink"
182
- | "rose";
181
+ | "rose"
182
+ | string;
183
183
 
184
184
  export interface Directives {
185
185
  tooltip?: Partial<Props>;
@@ -23,16 +23,15 @@ import { COMPONENT_NAME } from "./constants.ts";
23
23
  import { vClickOutside } from "../directives";
24
24
 
25
25
  import type { ComputedRef } from "vue";
26
- import type { UDatePickerProps, Config, Locale } from "./types.ts";
26
+ import type { Props, Config, Locale } from "./types.ts";
27
27
  import type { ComponentExposed } from "../types.ts";
28
28
  import type { Config as UCalendarConfig } from "../ui.form-calendar/types.ts";
29
29
  import type { DateLocale } from "../ui.form-calendar/utilFormatting.ts";
30
30
 
31
31
  defineOptions({ inheritAttrs: false });
32
32
 
33
- type Props = UDatePickerProps<TModelValue>;
34
- const props = withDefaults(defineProps<Props>(), {
35
- ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
33
+ const props = withDefaults(defineProps<Props<TModelValue>>(), {
34
+ ...getDefaults<Props<TModelValue>, Config>(defaultConfig, COMPONENT_NAME),
36
35
  modelValue: undefined,
37
36
  minDate: undefined,
38
37
  maxDate: undefined,
@@ -12,13 +12,13 @@ import URow from "../../ui.container-row/URow.vue";
12
12
 
13
13
  import { COMPONENT_NAME } from "../constants.ts";
14
14
 
15
- import type { UDatePickerProps } from "../types.ts";
15
+ import type { Props } from "../types.ts";
16
16
 
17
- interface DefaultUDatePickerArgs extends UDatePickerProps<unknown> {
17
+ interface DefaultUDatePickerArgs extends Props<unknown> {
18
18
  slotTemplate?: string;
19
19
  }
20
20
 
21
- interface EnumUDatePickerArgs extends UDatePickerProps<unknown> {
21
+ interface EnumUDatePickerArgs extends Props<unknown> {
22
22
  slotTemplate?: string;
23
23
  enum: "size";
24
24
  }
@@ -5,7 +5,7 @@ import type { ComponentConfig } from "../types.ts";
5
5
  export type Locale = typeof defaultConfig.i18n;
6
6
  export type Config = typeof defaultConfig;
7
7
 
8
- export interface UDatePickerProps<TModelValue> {
8
+ export interface Props<TModelValue> {
9
9
  /**
10
10
  * Calendar value (JavaScript Date object or string formatted in given `dateFormat` or object when `range` enabled).
11
11
  */
@@ -17,7 +17,6 @@ const props = withDefaults(defineProps<StepperProgressProps>(), {
17
17
 
18
18
  const stepperColor = computed(() => {
19
19
  const isValidColor = (color: string): color is keyof typeof colors => color in colors;
20
-
21
20
  const isGrayColor = (color: string): color is keyof typeof colors => GRAY_COLORS.includes(color);
22
21
 
23
22
  if (isValidColor(props.color)) {
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import { computed, useId, watch } from "vue";
3
+
4
+ import { vTooltip } from "../directives";
5
+ import useUI from "../composables/useUI.ts";
6
+ import { getDefaults } from "../utils/ui.ts";
7
+ import { setTheme, getSelectedBrandColor, getSelectedGrayColor } from "../utils/theme.ts";
8
+ import { GRAYSCALE_COLOR } from "../constants.js";
9
+
10
+ import UDivider from "../ui.container-divider/UDivider.vue";
11
+ import UButton from "../ui.button/UButton.vue";
12
+
13
+ import { COMPONENT_NAME } from "./constants.ts";
14
+ import defaultConfig from "./config.ts";
15
+
16
+ import type { BrandColors, GrayColors } from "../types.ts";
17
+ import type { Props, Config } from "./types.ts";
18
+
19
+ defineOptions({ inheritAttrs: false });
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
23
+ modelValue: () => ["", ""],
24
+ brandColors: () => ({}),
25
+ grayColors: () => ({}),
26
+ brandLabels: () => ({}),
27
+ grayLabels: () => ({}),
28
+ });
29
+
30
+ const emit = defineEmits([
31
+ /**
32
+ * Triggers when color value changes.
33
+ * @property {string} value
34
+ */
35
+ "update:modelValue",
36
+ ]);
37
+
38
+ const elementId = props.id || useId();
39
+
40
+ const selectedBrand = getSelectedBrandColor();
41
+ const selectedGray = getSelectedGrayColor();
42
+
43
+ const selectedItem = computed({
44
+ get: () => {
45
+ const [brand, gray] = props.modelValue;
46
+
47
+ return [brand || selectedBrand, gray || selectedGray];
48
+ },
49
+ set: (value) => emit("update:modelValue", value),
50
+ });
51
+
52
+ watch(selectedItem, (newValue, oldValue) => {
53
+ const [oldBrand, oldGray] = oldValue;
54
+ const [brand, gray] = newValue;
55
+
56
+ if (oldBrand === brand && oldGray === gray) {
57
+ return;
58
+ }
59
+
60
+ setTheme({ brand, gray });
61
+
62
+ if (oldBrand !== brand && (brand === GRAYSCALE_COLOR || oldBrand === GRAYSCALE_COLOR)) {
63
+ window.location.reload();
64
+ }
65
+ });
66
+
67
+ function onClickBrandColor(brand: BrandColors) {
68
+ const [, gray] = selectedItem.value;
69
+
70
+ selectedItem.value = [brand, gray];
71
+ }
72
+
73
+ function onClickGrayColor(gray: GrayColors) {
74
+ const [brand] = selectedItem.value;
75
+
76
+ selectedItem.value = [brand, gray];
77
+ }
78
+
79
+ /**
80
+ * Get element / nested component attributes for each config token ✨
81
+ * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
82
+ */
83
+ const { listAttrs, colorButtonAttrs, circleAttrs, colorDividerAttrs } =
84
+ useUI<Config>(defaultConfig);
85
+ </script>
86
+
87
+ <template>
88
+ <div :id="elementId" v-bind="listAttrs">
89
+ <UButton
90
+ v-for="(brandColorClass, color) in brandColors"
91
+ :key="color"
92
+ v-tooltip="brandLabels?.[color] || color"
93
+ square
94
+ size="xs"
95
+ :ring="false"
96
+ color="grayscale"
97
+ variant="thirdary"
98
+ :filled="selectedItem[0] === color"
99
+ v-bind="colorButtonAttrs"
100
+ @click="onClickBrandColor(color)"
101
+ >
102
+ <div :class="brandColorClass" v-bind="circleAttrs" />
103
+ </UButton>
104
+
105
+ <UDivider size="xs" v-bind="colorDividerAttrs" />
106
+
107
+ <UButton
108
+ v-for="(grayColorClass, color) in grayColors"
109
+ :key="color"
110
+ v-tooltip="brandLabels?.[color] || color"
111
+ square
112
+ size="xs"
113
+ :ring="false"
114
+ color="grayscale"
115
+ variant="thirdary"
116
+ :filled="selectedItem[1] === color"
117
+ v-bind="colorButtonAttrs"
118
+ @click="onClickGrayColor(color)"
119
+ >
120
+ <div :class="grayColorClass" v-bind="circleAttrs" />
121
+ </UButton>
122
+ </div>
123
+ </template>
@@ -0,0 +1,20 @@
1
+ export default /*tw*/ {
2
+ list: "flex flex-wrap gap-0.5",
3
+ colorButton: {
4
+ base: "{UButton}",
5
+ button: {
6
+ compoundVariants: [
7
+ {
8
+ filled: true,
9
+ variant: "thirdary",
10
+ class: "!bg-gray-800/10 dark:!bg-gray-200/10",
11
+ },
12
+ ],
13
+ },
14
+ },
15
+ circle: "size-5 rounded-full",
16
+ colorDivider: "{UDivider}",
17
+ defaults: {
18
+ size: "md",
19
+ },
20
+ };
@@ -2,4 +2,4 @@
2
2
  This const is needed to prevent the issue in script setup:
3
3
  `defineProps` is referencing locally declared variables. (vue/valid-define-props)
4
4
  */
5
- export const COMPONENT_NAME = "UColorPicker";
5
+ export const COMPONENT_NAME = "UThemeColorToggle";
@@ -0,0 +1,111 @@
1
+ import {
2
+ getArgTypes,
3
+ getSlotNames,
4
+ getSlotsFragment,
5
+ getDocsDescription,
6
+ } from "../../utils/storybook.ts";
7
+
8
+ import UThemeColorToggle from "../UThemeColorToggle.vue";
9
+ import UCol from "../../ui.container-col/UCol.vue";
10
+ import UButton from "../../ui.button/UButton.vue";
11
+
12
+ import type { Meta, StoryFn } from "@storybook/vue3";
13
+ import type { Props } from "../types.ts";
14
+
15
+ interface UThemeColorToggleArgs extends Props {
16
+ slotTemplate?: string;
17
+ enum: "size";
18
+ }
19
+
20
+ export default {
21
+ id: "100020",
22
+ title: "Other / Theme Color Switcher",
23
+ component: UThemeColorToggle,
24
+ args: {
25
+ modelValue: ["", ""],
26
+ brandColors: {
27
+ grayscale: "bg-gray-900",
28
+ gray: "bg-gray-600",
29
+ red: "bg-red-600",
30
+ orange: "bg-orange-600",
31
+ amber: "bg-amber-600",
32
+ yellow: "bg-yellow-600",
33
+ lime: "bg-lime-600",
34
+ green: "bg-green-600",
35
+ emerald: "bg-emerald-600",
36
+ teal: "bg-teal-600",
37
+ cyan: "bg-cyan-600",
38
+ sky: "bg-sky-600",
39
+ blue: "bg-blue-600",
40
+ indigo: "bg-indigo-600",
41
+ violet: "bg-violet-600",
42
+ purple: "bg-purple-600",
43
+ fuchsia: "bg-fuchsia-600",
44
+ pink: "bg-pink-600",
45
+ rose: "bg-rose-600",
46
+ },
47
+ grayColors: {
48
+ slate: "bg-slate-600",
49
+ cool: "bg-cool-600",
50
+ zinc: "bg-zinc-600",
51
+ neutral: "bg-neutral-600",
52
+ stone: "bg-stone-600",
53
+ },
54
+ },
55
+ argTypes: {
56
+ ...getArgTypes(UThemeColorToggle.__name),
57
+ },
58
+ parameters: {
59
+ docs: {
60
+ ...getDocsDescription(UThemeColorToggle.__name),
61
+ },
62
+ },
63
+ } as Meta;
64
+
65
+ const DefaultTemplate: StoryFn<UThemeColorToggleArgs> = (args: UThemeColorToggleArgs) => ({
66
+ components: { UThemeColorToggle, UButton, UCol },
67
+ setup() {
68
+ const slots = getSlotNames(UThemeColorToggle.__name);
69
+
70
+ return { args, slots };
71
+ },
72
+ template: `
73
+ <UCol>
74
+ <UThemeColorToggle v-bind="args" v-model="args.modelValue">
75
+ ${args.slotTemplate || getSlotsFragment("")}
76
+ </UThemeColorToggle>
77
+
78
+ <UButton label="Brand button" color="brand"/>
79
+ </UCol>
80
+ `,
81
+ });
82
+
83
+ const EnumVariantTemplate: StoryFn<UThemeColorToggleArgs> = (
84
+ args: UThemeColorToggleArgs,
85
+ { argTypes },
86
+ ) => ({
87
+ components: { UCol, UThemeColorToggle },
88
+ setup() {
89
+ return {
90
+ args,
91
+ options: argTypes?.[args.enum]?.options,
92
+ };
93
+ },
94
+ template: `
95
+ <UCol>
96
+ <UThemeColorToggle
97
+ v-for="(option, index) in options"
98
+ :key="index"
99
+ v-bind="args"
100
+ v-model="args.modelValue"
101
+ :[args.enum]="option"
102
+ />
103
+ </UCol>
104
+ `,
105
+ });
106
+
107
+ export const Default = DefaultTemplate.bind({});
108
+ Default.args = {};
109
+
110
+ export const Sizes = EnumVariantTemplate.bind({});
111
+ Sizes.args = { enum: "size" };
@@ -0,0 +1,52 @@
1
+ import defaultConfig from "./config.ts";
2
+
3
+ import type { BrandColors, GrayColors, ComponentConfig } from "../types.ts";
4
+
5
+ export type Config = typeof defaultConfig;
6
+
7
+ export interface Props {
8
+ /**
9
+ * Selected values.
10
+ */
11
+ modelValue: [BrandColors, GrayColors];
12
+
13
+ /**
14
+ * Component size.
15
+ */
16
+ size?: "sm" | "md" | "lg";
17
+
18
+ /**
19
+ * Brand color list.
20
+ */
21
+ brandColors?: Record<BrandColors, string>;
22
+
23
+ /**
24
+ * Gray color list.
25
+ */
26
+ grayColors?: Record<GrayColors, string>;
27
+
28
+ /**
29
+ * Brand color labels.
30
+ */
31
+ brandLabels?: Record<BrandColors, string>;
32
+
33
+ /**
34
+ * Gray color labels.
35
+ */
36
+ grayLabels?: Record<GrayColors, string>;
37
+
38
+ /**
39
+ * Unique element id.
40
+ */
41
+ id?: string;
42
+
43
+ /**
44
+ * Component config object.
45
+ */
46
+ config?: ComponentConfig<Config>;
47
+
48
+ /**
49
+ * Data-test attribute for automated testing.
50
+ */
51
+ dataTest?: string;
52
+ }
@@ -11,7 +11,7 @@ const CLOSING_BRACKET = "}";
11
11
  const IGNORE_PROP = "@ignore";
12
12
  const CUSTOM_PROP = "@custom";
13
13
 
14
- const PROPS_INTERFACE_REG_EXP = /export\s+interface\s+Props\s*{([^}]*)}/s;
14
+ const PROPS_INTERFACE_REG_EXP = /export\s+interface\s+Props(?:<\w+>)?\s*{([^}]*)}/s;
15
15
  const UNION_SYMBOLS_REG_EXP = /[?|:"|;]/g;
16
16
  const WORD_IN_QUOTE_REG_EXP = /"([^"]+)"/g;
17
17
 
@@ -129,6 +129,11 @@ async function modifyComponentTypes(filePath, props) {
129
129
  const defaultOptionalMark = lines[propIndex]?.includes(OPTIONAL_MARK) ? OPTIONAL_MARK : "";
130
130
  const optionalMark = required === undefined ? defaultOptionalMark : userOptionalMark;
131
131
 
132
+ const isExtendOnly = lines
133
+ .slice(propIndex - 2, propIndex)
134
+ .join("")
135
+ .includes("@extendOnly");
136
+
132
137
  const propDescription = description?.replaceAll(/[\n\s]+/g, " ").trim() || "–"; // removes new lines and double spaces.
133
138
  const propType = unionType.length ? unionType : type;
134
139
 
@@ -142,14 +147,14 @@ async function modifyComponentTypes(filePath, props) {
142
147
 
143
148
  /* Check if the prop type already exists. */
144
149
  if (~propIndex) {
145
- if (unionType.length && isAssignableValue) {
150
+ if (unionType.length && (isAssignableValue || !isExtendOnly)) {
146
151
  // Remove multiline union types;
147
152
  lines.splice(propIndex + 1, propEndIndex);
148
153
 
149
154
  lines.splice(propIndex, 1, ` ${name}${defaultOptionalMark}: ${propType};`);
150
155
  }
151
156
 
152
- if (unionType.length && !isAssignableValue) {
157
+ if (unionType.length && isExtendOnly && !isAssignableValue) {
153
158
  // eslint-disable-next-line no-console
154
159
  console.warn(`${unionType} is not assignable to type ${defaultUnionType}.`);
155
160
  }
@@ -147,15 +147,17 @@ export function createMergeConfigs(cx) {
147
147
  function findItem(config = []) {
148
148
  config = cloneDeep(config);
149
149
 
150
- const globalConfigUniqueItemIndex = globalCompoundVariants.findIndex(isSameItem);
151
- const propsConfigUniqueItemIndex = propsCompoundVariants.findIndex(isSameItem);
150
+ const globalConfigSimilarItemIndex = globalCompoundVariants.findIndex(isSameItem);
151
+ const propsConfigSimilarItemIndex = propsCompoundVariants.findIndex(isSameItem);
152
152
 
153
- if (~globalConfigUniqueItemIndex) {
154
- globalCompoundVariants.splice(globalConfigUniqueItemIndex, 1);
153
+ if (~globalConfigSimilarItemIndex) {
154
+ config.push(globalCompoundVariants[globalConfigSimilarItemIndex]);
155
+ globalCompoundVariants.splice(globalConfigSimilarItemIndex, 1);
155
156
  }
156
157
 
157
- if (~propsConfigUniqueItemIndex) {
158
- propsCompoundVariants.splice(propsConfigUniqueItemIndex, 1);
158
+ if (~propsConfigSimilarItemIndex) {
159
+ config.push(propsCompoundVariants[propsConfigSimilarItemIndex]);
160
+ propsCompoundVariants.splice(propsConfigSimilarItemIndex, 1);
159
161
  }
160
162
 
161
163
  return config.find(isSameItem);
@@ -222,10 +222,6 @@ async function findComponentColors(componentName, files, vuelessConfigFiles) {
222
222
  isComponentExists = Boolean(matchedComponent);
223
223
  }
224
224
 
225
- const propsColors = getPropsColors(fileContent);
226
-
227
- propsColors.forEach((color) => colors.add(color));
228
-
229
225
  if (isDefaultConfig) {
230
226
  fileContent.match(objectColorRegExp)?.forEach((colorMatch) => {
231
227
  const [, color] = objectColorRegExp.exec(colorMatch) || [];
@@ -264,16 +260,6 @@ async function findComponentColors(componentName, files, vuelessConfigFiles) {
264
260
  };
265
261
  }
266
262
 
267
- function getPropsColors(inputString) {
268
- const colorsMatch = inputString.match(/colors:\s*\[(.*?)\]/s);
269
-
270
- if (colorsMatch && colorsMatch[1]) {
271
- return colorsMatch[1].split(",").map((color) => color.trim().replace(/['"]/g, ""));
272
- }
273
-
274
- return [];
275
- }
276
-
277
263
  function isDefaultComponentConfig(filePath, componentName) {
278
264
  const componentDirName = filePath.split(path.sep).at(-2);
279
265
 
package/utils/theme.ts CHANGED
@@ -5,7 +5,12 @@ import { tailwindConfig } from "./tailwindConfig.ts";
5
5
  import { vuelessConfig } from "./ui.ts";
6
6
  import { isSSR, isCSR } from "./helper.ts";
7
7
  import {
8
- BRAND_COLORS,
8
+ PX_IN_REM,
9
+ COOL_COLOR,
10
+ GRAY_COLOR,
11
+ COLOR_MODE_KEY,
12
+ LIGHT_MODE_SELECTOR,
13
+ DARK_MODE_SELECTOR,
9
14
  GRAYSCALE_COLOR,
10
15
  DEFAULT_RING,
11
16
  DEFAULT_RING_OFFSET,
@@ -14,13 +19,6 @@ import {
14
19
  DEFAULT_GRAY_COLOR,
15
20
  DEFAULT_RING_OFFSET_COLOR_LIGHT,
16
21
  DEFAULT_RING_OFFSET_COLOR_DARK,
17
- DARK_MODE_SELECTOR,
18
- GRAY_COLORS,
19
- PX_IN_REM,
20
- COOL_COLOR,
21
- GRAY_COLOR,
22
- LIGHT_MODE_SELECTOR,
23
- COLOR_MODE_KEY,
24
22
  } from "../constants.js";
25
23
 
26
24
  import type {
@@ -35,6 +33,10 @@ import { ColorMode } from "../types.ts";
35
33
 
36
34
  type DefaultColors = typeof tailwindColors;
37
35
 
36
+ interface Colors extends DefaultColors {
37
+ [key: string]: Partial<TailwindColorShades> | string;
38
+ }
39
+
38
40
  export function themeInit() {
39
41
  if (isSSR) return;
40
42
 
@@ -97,46 +99,41 @@ export function setColorMode(colorMode: `${ColorMode}`) {
97
99
  }
98
100
  }
99
101
 
102
+ export function getSelectedBrandColor() {
103
+ return localStorage.getItem("brand") || undefined;
104
+ }
105
+
106
+ export function getSelectedGrayColor() {
107
+ return localStorage.getItem("gray") || undefined;
108
+ }
109
+
100
110
  export function setTheme(config: Config = {}) {
101
- setColorMode(vuelessConfig.colorMode || config?.colorMode || ColorMode.Light);
111
+ setColorMode(vuelessConfig.colorMode || config.colorMode || ColorMode.Light);
102
112
 
103
- const rounding = config?.rounding ?? vuelessConfig.rounding ?? DEFAULT_ROUNDING;
104
- const roundingSm = config?.roundingSm ?? vuelessConfig.roundingSm ?? rounding / 2;
105
- const roundingLg = config?.roundingLg ?? vuelessConfig.roundingLg ?? rounding * 2;
113
+ const rounding = config.rounding ?? vuelessConfig.rounding ?? DEFAULT_ROUNDING;
114
+ const roundingSm = config.roundingSm ?? vuelessConfig.roundingSm ?? rounding / 2;
115
+ const roundingLg = config.roundingLg ?? vuelessConfig.roundingLg ?? rounding * 2;
106
116
  const isDarkMode = isCSR && document.documentElement.classList.contains(DARK_MODE_SELECTOR);
107
117
 
108
- let brand: BrandColors | GrayColors | typeof GRAY_COLOR =
109
- config?.brand ?? vuelessConfig.brand ?? DEFAULT_BRAND_COLOR;
118
+ const brand: BrandColors =
119
+ config.brand ?? getSelectedBrandColor() ?? vuelessConfig.brand ?? DEFAULT_BRAND_COLOR;
110
120
 
111
- let gray: GrayColors | typeof GRAY_COLOR =
112
- config?.gray ?? vuelessConfig.gray ?? DEFAULT_GRAY_COLOR;
121
+ let gray: GrayColors =
122
+ config.gray ?? getSelectedGrayColor() ?? vuelessConfig.gray ?? DEFAULT_GRAY_COLOR;
113
123
 
114
- const ring = config?.ring ?? vuelessConfig.ring ?? DEFAULT_RING;
115
- const ringOffset = config?.ringOffset ?? vuelessConfig.ringOffset ?? DEFAULT_RING_OFFSET;
124
+ const ring = config.ring ?? vuelessConfig.ring ?? DEFAULT_RING;
125
+ const ringOffset = config.ringOffset ?? vuelessConfig.ringOffset ?? DEFAULT_RING_OFFSET;
116
126
 
117
127
  const ringOffsetColorDark =
118
- config?.ringOffsetColorDark ??
128
+ config.ringOffsetColorDark ??
119
129
  vuelessConfig.ringOffsetColorDark ??
120
130
  DEFAULT_RING_OFFSET_COLOR_DARK;
121
131
 
122
132
  const ringOffsetColorLight =
123
- config?.ringOffsetColorLight ??
133
+ config.ringOffsetColorLight ??
124
134
  vuelessConfig.ringOffsetColorLight ??
125
135
  DEFAULT_RING_OFFSET_COLOR_LIGHT;
126
136
 
127
- const isBrandColor = [...BRAND_COLORS, GRAYSCALE_COLOR].some((color) => color === brand);
128
- const isGrayColor = GRAY_COLORS.some((color) => color === gray);
129
-
130
- if (!isBrandColor) {
131
- // eslint-disable-next-line no-console
132
- console.warn(`Brand color '${brand}' is incorrect.`);
133
- }
134
-
135
- if (!isGrayColor) {
136
- // eslint-disable-next-line no-console
137
- console.warn(`Gray color '${gray}' is incorrect.`);
138
- }
139
-
140
137
  const defaultBrandShade = isDarkMode ? 400 : 600;
141
138
  const defaultGrayShade = isDarkMode ? 400 : 600;
142
139
  const defaultRingOffsetColor = isDarkMode ? ringOffsetColorDark : ringOffsetColorLight;
@@ -145,10 +142,6 @@ export function setTheme(config: Config = {}) {
145
142
  gray = GRAY_COLOR;
146
143
  }
147
144
 
148
- if (brand === GRAYSCALE_COLOR) {
149
- brand = gray;
150
- }
151
-
152
145
  /* Remove deprecated color aliases. */
153
146
  delete (tailwindColors as Partial<DefaultColors>).lightBlue;
154
147
  delete (tailwindColors as Partial<DefaultColors>).warmGray;
@@ -156,12 +149,29 @@ export function setTheme(config: Config = {}) {
156
149
  delete (tailwindColors as Partial<DefaultColors>).coolGray;
157
150
  delete (tailwindColors as Partial<DefaultColors>).blueGray;
158
151
 
159
- const colors: DefaultColors = merge(
160
- tailwindColors,
152
+ const colors: Colors = merge(
153
+ tailwindColors as Colors,
161
154
  tailwindConfig?.theme?.extend?.colors || {},
162
155
  vuelessConfig.tailwindTheme?.extend?.colors || {},
163
156
  );
164
157
 
158
+ const projectColors = Object.keys(colors);
159
+ const isBrandColor = projectColors.some((color) => color === brand) || brand === GRAYSCALE_COLOR;
160
+ const isGrayColor = projectColors.some((color) => color === gray);
161
+
162
+ if (!isBrandColor) {
163
+ // eslint-disable-next-line no-console
164
+ console.warn(`The brand color '${brand}' is missing in your palette.`);
165
+ }
166
+
167
+ if (!isGrayColor) {
168
+ // eslint-disable-next-line no-console
169
+ console.warn(`The gray color '${gray}' is missing in your palette.`);
170
+ }
171
+
172
+ localStorage.setItem("brand", brand);
173
+ localStorage.setItem("gray", gray);
174
+
165
175
  const variables: Partial<VuelessCssVariables> = {
166
176
  "--vl-rounding-sm": `${Number(roundingSm) / PX_IN_REM}rem`,
167
177
  "--vl-rounding": `${Number(rounding) / PX_IN_REM}rem`,
@@ -169,23 +179,23 @@ export function setTheme(config: Config = {}) {
169
179
  "--vl-ring": `${ring}px`,
170
180
  "--vl-ring-offset": `${ringOffset}px`,
171
181
  "--vl-ring-offset-color": convertHexInRgb(defaultRingOffsetColor),
172
- "--vl-color-gray-default": convertHexInRgb(colors[gray][defaultBrandShade]),
173
- "--vl-color-brand-default": convertHexInRgb(colors[brand][defaultGrayShade]),
182
+ "--vl-color-gray-default": convertHexInRgb(colors[gray]?.[defaultBrandShade]),
183
+ "--vl-color-brand-default": convertHexInRgb(colors[brand]?.[defaultGrayShade]),
174
184
  };
175
185
 
176
- for (const key in colors[gray]) {
186
+ for (const key in colors[gray] as TailwindColorShades) {
177
187
  const shade = key as unknown as keyof TailwindColorShades;
178
188
 
179
189
  variables[`--vl-color-gray-${key}` as keyof VuelessCssVariables] = convertHexInRgb(
180
- colors[gray][shade],
190
+ colors[gray]?.[shade],
181
191
  );
182
192
  }
183
193
 
184
- for (const key in colors[brand]) {
194
+ for (const key in colors[brand] as TailwindColorShades) {
185
195
  const shade = key as unknown as keyof TailwindColorShades;
186
196
 
187
197
  variables[`--vl-color-brand-${key}` as keyof VuelessCssVariables] = convertHexInRgb(
188
- colors[brand][shade],
198
+ colors[brand]?.[shade],
189
199
  );
190
200
  }
191
201
 
@@ -205,7 +215,9 @@ export function setTheme(config: Config = {}) {
205
215
  return rootVariables;
206
216
  }
207
217
 
208
- export function convertHexInRgb(hex: string) {
218
+ export function convertHexInRgb(hex?: string) {
219
+ if (!hex) return;
220
+
209
221
  const color = hex.replace(/#/g, "");
210
222
 
211
223
  let r, g, b;
package/utils/ui.ts CHANGED
@@ -4,10 +4,10 @@ import { extendTailwindMerge } from "tailwind-merge";
4
4
  import { isCSR, isSSR } from "./helper.ts";
5
5
  import { createGetMergedConfig } from "./node/mergeConfigs.js";
6
6
  import { COMPONENT_NAME as U_ICON } from "../ui.image-icon/constants.ts";
7
+ import { getSelectedBrandColor } from "./theme.ts";
7
8
  import {
8
9
  BRAND_COLOR,
9
10
  GRAYSCALE_COLOR,
10
- DEFAULT_BRAND_COLOR,
11
11
  ICON_NON_PROPS_DEFAULTS,
12
12
  TAILWIND_MERGE_EXTENSION,
13
13
  NESTED_COMPONENT_PATTERN_REG_EXP,
@@ -129,10 +129,13 @@ export function getDefaults<Props, Config>(defaultConfig: Config, name: Componen
129
129
  * Otherwise return given color.
130
130
  */
131
131
  export function getColor(color: string) {
132
- const isBrandColorGrayscale = (vuelessConfig.brand ?? DEFAULT_BRAND_COLOR) === GRAYSCALE_COLOR;
132
+ const isBrandColorGrayscale = vuelessConfig.brand === GRAYSCALE_COLOR;
133
+ const isSelectedColorGrayscale = getSelectedBrandColor() === GRAYSCALE_COLOR;
133
134
  const isComponentColorBrand = color === BRAND_COLOR;
134
135
 
135
- return isBrandColorGrayscale && isComponentColorBrand ? GRAYSCALE_COLOR : color;
136
+ return (isBrandColorGrayscale || isSelectedColorGrayscale) && isComponentColorBrand
137
+ ? GRAYSCALE_COLOR
138
+ : color;
136
139
  }
137
140
 
138
141
  /**
@@ -1,118 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, useId } from "vue";
3
-
4
- import useUI from "../composables/useUI.ts";
5
- import { getDefaults } from "../utils/ui.ts";
6
-
7
- import UIcon from "../ui.image-icon/UIcon.vue";
8
- import URadio from "../ui.form-radio/URadio.vue";
9
- import ULabel from "../ui.form-label/ULabel.vue";
10
-
11
- import { COMPONENT_NAME } from "./constants.ts";
12
- import defaultConfig from "./config.ts";
13
-
14
- import type { Props, Config } from "./types.ts";
15
- import type { BrandColors } from "../types.ts";
16
-
17
- defineOptions({ inheritAttrs: false });
18
-
19
- const props = withDefaults(defineProps<Props>(), {
20
- ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
21
- colorOptions: () =>
22
- getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME).colorOptions as BrandColors[],
23
- modelValue: "",
24
- label: "",
25
- });
26
-
27
- const emit = defineEmits([
28
- /**
29
- * Triggers when color value changes.
30
- * @property {string} value
31
- */
32
- "update:modelValue",
33
- ]);
34
-
35
- const elementId = props.id || useId();
36
-
37
- const selectedItem = computed({
38
- get: () => props.modelValue,
39
- set: (value) => emit("update:modelValue", value),
40
- });
41
-
42
- function onUpdateValue(value: string) {
43
- selectedItem.value = value;
44
- }
45
-
46
- /**
47
- * Get element / nested component attributes for each config token ✨
48
- * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
49
- */
50
- const {
51
- config,
52
- colorPickerLabelAttrs,
53
- listAttrs,
54
- colorPickerRadioAttrs,
55
- unselectedColorPickerRadioAttrs,
56
- unselectedIconAttrs,
57
- unselectedAttrs,
58
- } = useUI<Config>(defaultConfig);
59
- </script>
60
-
61
- <template>
62
- <ULabel
63
- :label="label"
64
- :description="description"
65
- :disabled="disabled"
66
- :error="error"
67
- :size="size"
68
- align="topWithDesc"
69
- v-bind="colorPickerLabelAttrs"
70
- :data-test="dataTest"
71
- >
72
- <template #label>
73
- <!--
74
- @slot Use this to add custom content instead of the label.
75
- @binding {string} label
76
- -->
77
- <slot name="label" :label="label" />
78
- </template>
79
-
80
- <div v-bind="listAttrs">
81
- <div v-bind="unselectedAttrs">
82
- <URadio
83
- :id="elementId"
84
- :name="name"
85
- :size="size"
86
- color="gray"
87
- :checked="selectedItem === ''"
88
- :disabled="disabled"
89
- v-bind="unselectedColorPickerRadioAttrs"
90
- @update:model-value="onUpdateValue('')"
91
- />
92
-
93
- <label :for="elementId">
94
- <UIcon
95
- v-if="selectedItem === ''"
96
- internal
97
- color="gray"
98
- :name="config.defaults.unselectedIcon"
99
- v-bind="unselectedIconAttrs"
100
- />
101
- </label>
102
- </div>
103
-
104
- <URadio
105
- v-for="(color, index) in colorOptions"
106
- :key="index"
107
- :name="name"
108
- :size="size"
109
- :value="color"
110
- :color="color"
111
- :checked="selectedItem === color"
112
- :disabled="disabled"
113
- v-bind="colorPickerRadioAttrs"
114
- @update:model-value="onUpdateValue(color)"
115
- />
116
- </div>
117
- </ULabel>
118
- </template>
@@ -1,63 +0,0 @@
1
- export default /*tw*/ {
2
- colorPickerLabel: "{ULabel}",
3
- list: {
4
- base: "flex flex-wrap",
5
- variants: {
6
- size: {
7
- sm: "gap-2 mt-px",
8
- md: "gap-3 mt-0.5",
9
- lg: "gap-3 mt-1",
10
- },
11
- },
12
- },
13
- unselected: "relative flex",
14
- unselectedIcon: {
15
- base: "{UIcon} absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full",
16
- defaults: {
17
- size: {
18
- xs: "3xs",
19
- sm: "2xs",
20
- md: "xs",
21
- lg: "sm",
22
- xl: "md",
23
- },
24
- },
25
- },
26
- unselectedColorPickerRadio: {
27
- base: "{URadio}",
28
- radio: "checked:text-white !border-gray-300",
29
- },
30
- colorPickerRadio: {
31
- base: "{URadio}",
32
- radio: `
33
- bg-{color}-600 border-{color}-600 hover:border-{color}-600 active:bg-{color}-800
34
- disabled:border-{color}-400 disabled:bg-{color}-400
35
- `,
36
- },
37
- defaults: {
38
- size: "md",
39
- name: "colorPicker",
40
- disabled: false,
41
- colorOptions: [
42
- "red",
43
- "orange",
44
- "amber",
45
- "yellow",
46
- "lime",
47
- "green",
48
- "emerald",
49
- "teal",
50
- "cyan",
51
- "sky",
52
- "blue",
53
- "indigo",
54
- "violet",
55
- "purple",
56
- "fuchsia",
57
- "pink",
58
- "rose",
59
- ],
60
- /* icons */
61
- unselectedIcon: "close",
62
- },
63
- };
@@ -1,86 +0,0 @@
1
- import {
2
- getArgTypes,
3
- getSlotNames,
4
- getSlotsFragment,
5
- getDocsDescription,
6
- } from "../../utils/storybook.ts";
7
-
8
- import UColorPicker from "../../ui.form-color-picker/UColorPicker.vue";
9
- import UCol from "../../ui.container-col/UCol.vue";
10
-
11
- import type { Meta, StoryFn } from "@storybook/vue3";
12
- import type { Props } from "../types.ts";
13
-
14
- interface UColorPickerArgs extends Props {
15
- slotTemplate?: string;
16
- enum: "size";
17
- }
18
-
19
- export default {
20
- id: "3210",
21
- title: "Form Inputs & Controls / Color Picker",
22
- component: UColorPicker,
23
- args: {
24
- label: "Label",
25
- modelValue: "",
26
- },
27
- argTypes: {
28
- ...getArgTypes(UColorPicker.__name),
29
- },
30
- parameters: {
31
- docs: {
32
- ...getDocsDescription(UColorPicker.__name),
33
- },
34
- },
35
- } as Meta;
36
-
37
- const DefaultTemplate: StoryFn<UColorPickerArgs> = (args: UColorPickerArgs) => ({
38
- components: { UColorPicker },
39
- setup() {
40
- const slots = getSlotNames(UColorPicker.__name);
41
-
42
- return { args, slots };
43
- },
44
- template: `
45
- <UColorPicker v-bind="args" v-model="args.modelValue">
46
- ${args.slotTemplate || getSlotsFragment("")}
47
- </UColorPicker>
48
- `,
49
- });
50
-
51
- const EnumVariantTemplate: StoryFn<UColorPickerArgs> = (args: UColorPickerArgs, { argTypes }) => ({
52
- components: { UCol, UColorPicker },
53
- setup() {
54
- return {
55
- args,
56
- options: argTypes?.[args.enum]?.options,
57
- };
58
- },
59
- template: `
60
- <UCol>
61
- <UColorPicker
62
- v-for="(option, index) in options"
63
- :key="index"
64
- v-bind="args"
65
- v-model="args.modelValue"
66
- :[args.enum]="option"
67
- :name="option"
68
- />
69
- </UCol>
70
- `,
71
- });
72
-
73
- export const Default = DefaultTemplate.bind({});
74
- Default.args = { name: "Default" };
75
-
76
- export const Sizes = EnumVariantTemplate.bind({});
77
- Sizes.args = { name: "Sizes", enum: "size" };
78
-
79
- export const Description = DefaultTemplate.bind({});
80
- Description.args = { name: "Description", description: "Description" };
81
-
82
- export const Error = DefaultTemplate.bind({});
83
- Error.args = { name: "Error", error: "some error" };
84
-
85
- export const Disabled = DefaultTemplate.bind({});
86
- Disabled.args = { name: "Disabled", disabled: true };
@@ -1,62 +0,0 @@
1
- import defaultConfig from "./config.ts";
2
-
3
- import type { BrandColors, ComponentConfig } from "../types.ts";
4
-
5
- export type Config = typeof defaultConfig;
6
-
7
- export interface Props {
8
- /**
9
- * Color picker selected value.
10
- */
11
- modelValue?: string;
12
-
13
- /**
14
- * Color picker name.
15
- */
16
- name?: string;
17
-
18
- /**
19
- * Color picker label.
20
- */
21
- label?: string;
22
-
23
- /**
24
- * Color picker description.
25
- */
26
- description?: string;
27
-
28
- /**
29
- * Error message.
30
- */
31
- error?: string;
32
-
33
- /**
34
- * Color picker size.
35
- */
36
- size?: "sm" | "md" | "lg";
37
-
38
- /**
39
- * Color picker color list.
40
- */
41
- colorOptions?: BrandColors[];
42
-
43
- /**
44
- * Set color picker disabled.
45
- */
46
- disabled?: boolean;
47
-
48
- /**
49
- * Unique element id.
50
- */
51
- id?: string;
52
-
53
- /**
54
- * Component config object.
55
- */
56
- config?: ComponentConfig<Config>;
57
-
58
- /**
59
- * Data-test attribute for automated testing.
60
- */
61
- dataTest?: string;
62
- }