@finema/core 2.19.1 → 2.20.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.
Files changed (28) hide show
  1. package/README.md +79 -79
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -1
  4. package/dist/runtime/components/DevToolsWindow/index.vue +98 -95
  5. package/dist/runtime/components/Form/FieldWrapper.vue +13 -13
  6. package/dist/runtime/components/Form/InputCheckbox/index.vue +18 -18
  7. package/dist/runtime/components/Form/InputNumber/index.vue +20 -20
  8. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +43 -43
  9. package/dist/runtime/components/Form/InputText/index.vue +216 -79
  10. package/dist/runtime/components/Form/InputText/types.d.ts +1 -0
  11. package/dist/runtime/components/Form/InputTextarea/index.vue +18 -18
  12. package/dist/runtime/components/Form/InputToggle/index.vue +17 -17
  13. package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue +1 -1
  14. package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue +1 -1
  15. package/dist/runtime/components/Form/index.vue +5 -5
  16. package/dist/runtime/components/Image.vue +28 -28
  17. package/dist/runtime/components/Log/index.vue +17 -17
  18. package/dist/runtime/components/Table/Base.vue +1 -1
  19. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  20. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  21. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  22. package/dist/runtime/components/Table/ColumnNumber.vue +1 -1
  23. package/dist/runtime/components/Table/ColumnText.vue +1 -1
  24. package/dist/runtime/components/Table/index.vue +3 -1
  25. package/dist/runtime/server/tsconfig.json +3 -3
  26. package/dist/runtime/theme/input.d.ts +3 -0
  27. package/dist/runtime/theme/input.js +4 -1
  28. package/package.json +3 -7
@@ -1,60 +1,90 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <Input
4
- v-if="type === 'password'"
5
- v-maska="activeMaskOptions"
6
- :model-value="value"
7
- :disabled="wrapperProps.disabled"
8
- :leading-icon="leadingIcon"
9
- :trailing-icon="trailingIcon"
10
- :loading="loading"
11
- :loading-icon="loadingIcon"
12
- :name="name"
13
- :placeholder="wrapperProps.placeholder"
14
- :type="isShowPassword ? 'text' : 'password'"
15
- :autofocus="!!autoFocus"
16
- :icon="icon"
17
- :readonly="readonly"
18
- :ui="defu(ui, { icon: { trailing: { pointer: '' } } })"
19
- @update:model-value="onChange"
20
- >
21
- <template #trailing>
22
- <Button
23
- color="neutral"
24
- variant="link"
25
- :icon="isShowPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
26
- :padded="false"
27
- @click="isShowPassword = !isShowPassword"
28
- />
29
- </template>
30
- </Input>
31
- <Input
32
- v-else
33
- v-maska="activeMaskOptions"
34
- :model-value="value"
35
- :disabled="wrapperProps.disabled"
36
- :leading-icon="leadingIcon"
37
- :trailing-icon="trailingIcon"
38
- :loading="loading"
39
- :loading-icon="loadingIcon"
40
- :name="name"
41
- :placeholder="wrapperProps.placeholder"
42
- :type="type"
43
- :autofocus="!!autoFocus"
44
- :icon="icon"
45
- :readonly="readonly"
46
- :ui="ui"
47
- @update:model-value="onChange"
48
- />
49
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div class="relative">
4
+ <Input
5
+ v-if="type === 'password'"
6
+ ref="inputRef"
7
+ v-maska="activeMaskOptions"
8
+ :model-value="value"
9
+ :disabled="wrapperProps.disabled"
10
+ :leading-icon="leadingIcon"
11
+ :trailing-icon="trailingIcon"
12
+ :loading="loading"
13
+ :loading-icon="loadingIcon"
14
+ :name="name"
15
+ :placeholder="wrapperProps.placeholder"
16
+ :type="isShowPassword ? 'text' : 'password'"
17
+ :autofocus="!!autoFocus"
18
+ :icon="icon"
19
+ :readonly="readonly"
20
+ :ui="defu(ui, { icon: { trailing: { pointer: '' } } })"
21
+ @update:model-value="onChange"
22
+ @focus="onFocus"
23
+ @blur="onBlur"
24
+ @keydown="onKeydown"
25
+ >
26
+ <template #trailing>
27
+ <Button
28
+ color="neutral"
29
+ variant="link"
30
+ :icon="isShowPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
31
+ :padded="false"
32
+ @click="isShowPassword = !isShowPassword"
33
+ />
34
+ </template>
35
+ </Input>
36
+ <Input
37
+ v-else
38
+ ref="inputRef"
39
+ v-maska="activeMaskOptions"
40
+ :model-value="value"
41
+ :disabled="wrapperProps.disabled"
42
+ :leading-icon="leadingIcon"
43
+ :trailing-icon="trailingIcon"
44
+ :loading="loading"
45
+ :loading-icon="loadingIcon"
46
+ :name="name"
47
+ :placeholder="wrapperProps.placeholder"
48
+ :type="type"
49
+ :autofocus="!!autoFocus"
50
+ :icon="icon"
51
+ :readonly="readonly"
52
+ :ui="ui"
53
+ @update:model-value="onChange"
54
+ @focus="onFocus"
55
+ @blur="onBlur"
56
+ @keydown="onKeydown"
57
+ />
58
+ <div
59
+ v-if="showSuggestions && filteredSuggestions.length > 0"
60
+ ref="suggestionsContainerRef"
61
+ :class="theme.suggestionsContainer()"
62
+ >
63
+ <div
64
+ v-for="(suggestion, index) in filteredSuggestions"
65
+ :key="suggestion"
66
+ :ref="(el) => setSuggestionItemRef(el, index)"
67
+ :class="[
68
+ theme.suggestionItem(),
69
+ { [theme.suggestionItemActive()]: index === selectedSuggestionIndex }
70
+ ]"
71
+ @mousedown.prevent="selectSuggestion(suggestion, index)"
72
+ @mouseenter="selectedSuggestionIndex = index"
73
+ >
74
+ {{ suggestion }}
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </FieldWrapper>
50
79
  </template>
51
80
 
52
81
  <script setup>
53
82
  import { vMaska } from "maska/vue";
54
83
  import { defu } from "defu";
55
- import { ref, computed } from "#imports";
84
+ import { ref, computed, nextTick, useUiConfig } from "#imports";
56
85
  import { useFieldHOC } from "#core/composables/useForm";
57
86
  import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
87
+ import { inputTheme } from "#core/theme/input";
58
88
  const emits = defineEmits(["change"]);
59
89
  const props = defineProps({
60
90
  type: { type: String, required: false, default: "text" },
@@ -69,6 +99,7 @@ const props = defineProps({
69
99
  maskEager: { type: Boolean, required: false },
70
100
  maskTokensReplace: { type: Boolean, required: false },
71
101
  maskReversed: { type: Boolean, required: false },
102
+ suggestions: { type: Array, required: false },
72
103
  form: { type: Object, required: false },
73
104
  name: { type: String, required: true },
74
105
  errorMessage: { type: String, required: false },
@@ -84,52 +115,158 @@ const props = defineProps({
84
115
  help: { type: String, required: false },
85
116
  ui: { type: null, required: false }
86
117
  });
118
+ const theme = computed(() => useUiConfig(inputTheme, "input")());
87
119
  const {
88
120
  value,
89
121
  wrapperProps,
90
122
  handleChange
91
123
  } = useFieldHOC(props);
92
124
  const isShowPassword = ref(false);
125
+ const inputRef = ref();
126
+ const showSuggestions = ref(false);
127
+ const selectedSuggestionIndex = ref(-1);
128
+ const suggestionsContainerRef = ref();
129
+ const suggestionItemRefs = ref([]);
130
+ const setSuggestionItemRef = (el, index) => {
131
+ if (suggestionItemRefs.value) {
132
+ suggestionItemRefs.value[index] = el;
133
+ }
134
+ };
93
135
  const onChange = (value2) => {
136
+ if (props.suggestions && props.suggestions.length > 0) {
137
+ showSuggestions.value = true;
138
+ selectedSuggestionIndex.value = -1;
139
+ }
94
140
  handleChange(value2);
95
141
  emits("change", value2);
96
142
  };
97
- const activeMaskOptions = computed(() => {
98
- if (props.maskOptions && Object.keys(props.maskOptions).length > 0) {
99
- if (typeof props.maskOptions.mask === "string" && Object.keys(props.maskOptions).length === 1) {
100
- return props.maskOptions.mask;
101
- }
102
- return props.maskOptions;
143
+ const filteredSuggestions = computed(() => {
144
+ if (!props.suggestions || !value.value) {
145
+ return props.suggestions || [];
103
146
  }
104
- const options = {};
105
- let hasIndividualProps = false;
106
- if (props.mask !== void 0) {
107
- options.mask = props.mask;
108
- hasIndividualProps = true;
147
+ const inputValue = value.value.toLowerCase();
148
+ return props.suggestions.filter(
149
+ (suggestion) => suggestion.toLowerCase().includes(inputValue)
150
+ );
151
+ });
152
+ const onFocus = () => {
153
+ if (props.suggestions && props.suggestions.length > 0) {
154
+ showSuggestions.value = true;
155
+ selectedSuggestionIndex.value = -1;
109
156
  }
110
- if (props.maskTokens !== void 0) {
111
- options.tokens = props.maskTokens;
112
- hasIndividualProps = true;
157
+ };
158
+ const onBlur = (event) => {
159
+ setTimeout(() => {
160
+ showSuggestions.value = false;
161
+ selectedSuggestionIndex.value = -1;
162
+ }, 150);
163
+ };
164
+ const onKeydown = (event) => {
165
+ if (!showSuggestions.value || filteredSuggestions.value.length === 0) {
166
+ return;
167
+ }
168
+ switch (event.key) {
169
+ case "ArrowDown":
170
+ event.preventDefault();
171
+ selectedSuggestionIndex.value = selectedSuggestionIndex.value < filteredSuggestions.value.length - 1 ? selectedSuggestionIndex.value + 1 : 0;
172
+ scrollToSelectedSuggestion();
173
+ break;
174
+ case "ArrowUp":
175
+ event.preventDefault();
176
+ selectedSuggestionIndex.value = selectedSuggestionIndex.value > 0 ? selectedSuggestionIndex.value - 1 : filteredSuggestions.value.length - 1;
177
+ scrollToSelectedSuggestion();
178
+ break;
179
+ case "Enter":
180
+ event.preventDefault();
181
+ if (selectedSuggestionIndex.value >= 0) {
182
+ selectSuggestion(filteredSuggestions.value[selectedSuggestionIndex.value], selectedSuggestionIndex.value);
183
+ }
184
+ break;
185
+ case "Escape":
186
+ showSuggestions.value = false;
187
+ selectedSuggestionIndex.value = -1;
188
+ break;
113
189
  }
114
- if (props.maskEager !== void 0) {
115
- options.eager = props.maskEager;
116
- hasIndividualProps = true;
190
+ };
191
+ const selectSuggestion = (suggestion, index) => {
192
+ if (index !== void 0) {
193
+ scrollToSuggestionByIndex(index);
117
194
  }
118
- if (props.maskReversed !== void 0) {
119
- options.reversed = props.maskReversed;
120
- hasIndividualProps = true;
195
+ handleChange(suggestion);
196
+ emits("change", suggestion);
197
+ showSuggestions.value = false;
198
+ selectedSuggestionIndex.value = -1;
199
+ nextTick(() => {
200
+ if (inputRef.value) {
201
+ inputRef.value.$el.querySelector("input")?.focus();
202
+ }
203
+ });
204
+ };
205
+ const scrollToSelectedSuggestion = () => {
206
+ nextTick(() => {
207
+ if (selectedSuggestionIndex.value >= 0) {
208
+ scrollToSuggestionByIndex(selectedSuggestionIndex.value);
209
+ }
210
+ });
211
+ };
212
+ const scrollToSuggestionByIndex = (index) => {
213
+ if (!suggestionsContainerRef.value || !suggestionItemRefs.value[index]) {
214
+ return;
121
215
  }
122
- if (props.maskTokensReplace !== void 0) {
123
- options.tokensReplace = props.maskTokensReplace;
124
- hasIndividualProps = true;
216
+ const container = suggestionsContainerRef.value;
217
+ const item = suggestionItemRefs.value[index];
218
+ if (item) {
219
+ const containerRect = container.getBoundingClientRect();
220
+ const itemRect = item.getBoundingClientRect();
221
+ const isAboveView = itemRect.top < containerRect.top;
222
+ const isBelowView = itemRect.bottom > containerRect.bottom;
223
+ if (isAboveView || isBelowView) {
224
+ item.scrollIntoView({
225
+ behavior: "smooth",
226
+ block: "nearest",
227
+ inline: "nearest"
228
+ });
229
+ }
125
230
  }
126
- if (hasIndividualProps) {
127
- const keys = Object.keys(options);
128
- if (keys.length === 1 && keys[0] === "mask" && typeof options.mask === "string") {
129
- return options.mask;
231
+ };
232
+ const activeMaskOptions = computed(
233
+ () => {
234
+ if (props.maskOptions && Object.keys(props.maskOptions).length > 0) {
235
+ if (typeof props.maskOptions.mask === "string" && Object.keys(props.maskOptions).length === 1) {
236
+ return props.maskOptions.mask;
237
+ }
238
+ return props.maskOptions;
239
+ }
240
+ const options = {};
241
+ let hasIndividualProps = false;
242
+ if (props.mask !== void 0) {
243
+ options.mask = props.mask;
244
+ hasIndividualProps = true;
245
+ }
246
+ if (props.maskTokens !== void 0) {
247
+ options.tokens = props.maskTokens;
248
+ hasIndividualProps = true;
130
249
  }
131
- return options;
250
+ if (props.maskEager !== void 0) {
251
+ options.eager = props.maskEager;
252
+ hasIndividualProps = true;
253
+ }
254
+ if (props.maskReversed !== void 0) {
255
+ options.reversed = props.maskReversed;
256
+ hasIndividualProps = true;
257
+ }
258
+ if (props.maskTokensReplace !== void 0) {
259
+ options.tokensReplace = props.maskTokensReplace;
260
+ hasIndividualProps = true;
261
+ }
262
+ if (hasIndividualProps) {
263
+ const keys = Object.keys(options);
264
+ if (keys.length === 1 && keys[0] === "mask" && typeof options.mask === "string") {
265
+ return options.mask;
266
+ }
267
+ return options;
268
+ }
269
+ return void 0;
132
270
  }
133
- return void 0;
134
- });
271
+ );
135
272
  </script>
@@ -13,6 +13,7 @@ export interface ITextFieldProps extends IFieldProps {
13
13
  maskEager?: boolean;
14
14
  maskTokensReplace?: boolean;
15
15
  maskReversed?: boolean;
16
+ suggestions?: string[];
16
17
  }
17
18
  export type ITextField = IFormFieldBase<INPUT_TYPES.TEXT | INPUT_TYPES.PASSWORD | INPUT_TYPES.EMAIL, ITextFieldProps, {
18
19
  change?: (value: string) => void;
@@ -1,22 +1,22 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <Textarea
4
- :model-value="value"
5
- :disabled="wrapperProps.disabled"
6
- :name="name"
7
- :resize="resize"
8
- :placeholder="wrapperProps.placeholder"
9
- :autofocus="!!autoFocus"
10
- :autoresize="autoresize"
11
- :rows="rows"
12
- :maxrows="maxrows"
13
- :loading="loading"
14
- :loading-icon="loadingIcon"
15
- :readonly="readonly"
16
- :ui="ui"
17
- @update:model-value="onChange"
18
- />
19
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <Textarea
4
+ :model-value="value"
5
+ :disabled="wrapperProps.disabled"
6
+ :name="name"
7
+ :resize="resize"
8
+ :placeholder="wrapperProps.placeholder"
9
+ :autofocus="!!autoFocus"
10
+ :autoresize="autoresize"
11
+ :rows="rows"
12
+ :maxrows="maxrows"
13
+ :loading="loading"
14
+ :loading-icon="loadingIcon"
15
+ :readonly="readonly"
16
+ :ui="ui"
17
+ @update:model-value="onChange"
18
+ />
19
+ </FieldWrapper>
20
20
  </template>
21
21
 
22
22
  <script setup>
@@ -1,21 +1,21 @@
1
1
  <template>
2
- <FieldWrapper
3
- v-bind="wrapperProps"
4
- label=""
5
- description=""
6
- >
7
- <Switch
8
- :model-value="value"
9
- :disabled="wrapperProps.disabled"
10
- :name="name"
11
- :ui="ui"
12
- :label="label"
13
- :description="description"
14
- :loading="loading"
15
- :loading-icon="loadingIcon"
16
- @update:modelValue="onChange"
17
- />
18
- </FieldWrapper>
2
+ <FieldWrapper
3
+ v-bind="wrapperProps"
4
+ label=""
5
+ description=""
6
+ >
7
+ <Switch
8
+ :model-value="value"
9
+ :disabled="wrapperProps.disabled"
10
+ :name="name"
11
+ :ui="ui"
12
+ :label="label"
13
+ :description="description"
14
+ :loading="loading"
15
+ :loading-icon="loadingIcon"
16
+ @update:modelValue="onChange"
17
+ />
18
+ </FieldWrapper>
19
19
  </template>
20
20
 
21
21
  <script setup>
@@ -6,7 +6,7 @@
6
6
  />
7
7
  <div :class="theme.labelWrapper()">
8
8
  <p
9
- class="cursor-pointer font-bold text-primary"
9
+ class="text-primary cursor-pointer font-bold"
10
10
  @click="$emit('openFile')"
11
11
  >
12
12
  {{ selectFileLabel }}
@@ -11,7 +11,7 @@
11
11
  <h1 class="truncate font-bold">
12
12
  {{ selectedFile.name }}
13
13
  </h1>
14
- <p class="truncate font-light text-error">
14
+ <p class="text-error truncate font-light">
15
15
  {{ uploadFailedLabel }}
16
16
  </p>
17
17
  <Button
@@ -1,5 +1,5 @@
1
- <template>
2
- <form class="form">
3
- <slot />
4
- </form>
5
- </template>
1
+ <template>
2
+ <form class="form">
3
+ <slot />
4
+ </form>
5
+ </template>
@@ -1,32 +1,32 @@
1
1
  <template>
2
- <UseImage v-bind="$props">
3
- <template #loading>
4
- <slot name="loading">
5
- <div
6
- class="flex h-full w-full items-center justify-center"
7
- >
8
- <Loader
9
- :loading="true"
10
- />
11
- </div>
12
- </slot>
13
- </template>
14
-
15
- <template #error>
16
- <slot name="error">
17
- <div
18
- class="flex h-full w-full items-center justify-center"
19
- >
20
- <p class="text-error-400">
21
- <Icon
22
- name="i-heroicons:exclamation-circle-solid"
23
- class="size-8 text-error-400"
24
- />
25
- </p>
26
- </div>
27
- </slot>
28
- </template>
29
- </UseImage>
2
+ <UseImage v-bind="$props">
3
+ <template #loading>
4
+ <slot name="loading">
5
+ <div
6
+ class="flex h-full w-full items-center justify-center"
7
+ >
8
+ <Loader
9
+ :loading="true"
10
+ />
11
+ </div>
12
+ </slot>
13
+ </template>
14
+
15
+ <template #error>
16
+ <slot name="error">
17
+ <div
18
+ class="flex h-full w-full items-center justify-center"
19
+ >
20
+ <p class="text-error-400">
21
+ <Icon
22
+ name="i-heroicons:exclamation-circle-solid"
23
+ class="text-error-400 size-8"
24
+ />
25
+ </p>
26
+ </div>
27
+ </slot>
28
+ </template>
29
+ </UseImage>
30
30
  </template>
31
31
 
32
32
  <script setup>
@@ -1,21 +1,21 @@
1
1
  <template>
2
- <DevOnly>
3
- <TeleportSafe
4
- to="#dev-logs"
5
- >
6
- <LogItem
7
- v-if="typeof data !== 'undefined'"
8
- :data="data"
9
- :title="title"
10
- />
11
- <LogItem
12
- v-for="(item, index) in dataItems"
13
- :key="index"
14
- :data="item"
15
- :title="`${title} #${index + 1}`"
16
- />
17
- </TeleportSafe>
18
- </DevOnly>
2
+ <DevOnly>
3
+ <TeleportSafe
4
+ to="#dev-logs"
5
+ >
6
+ <LogItem
7
+ v-if="typeof data !== 'undefined'"
8
+ :data="data"
9
+ :title="title"
10
+ />
11
+ <LogItem
12
+ v-for="(item, index) in dataItems"
13
+ :key="index"
14
+ :data="item"
15
+ :title="`${title} #${index + 1}`"
16
+ />
17
+ </TeleportSafe>
18
+ </DevOnly>
19
19
  </template>
20
20
 
21
21
  <script setup>
@@ -18,7 +18,7 @@
18
18
  <div class="flex h-60 items-center justify-center">
19
19
  <Icon
20
20
  name="i-svg-spinners:180-ring-with-bg"
21
- class="size-8 text-primary"
21
+ class="text-primary size-8"
22
22
  />
23
23
  </div>
24
24
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- {{ getValue || "-" }}
2
+ {{ getValue || "-" }}
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- {{ getValue || "-" }}
2
+ {{ getValue || "-" }}
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -1,8 +1,8 @@
1
1
  <template>
2
- <Image
3
- class="h-12 rounded"
4
- :src="getValue"
5
- />
2
+ <Image
3
+ class="h-12 rounded"
4
+ :src="getValue"
5
+ />
6
6
  </template>
7
7
 
8
8
  <script setup>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- {{ getValue }}
2
+ {{ getValue }}
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- {{ getValue }}
2
+ {{ getValue }}
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -32,7 +32,9 @@
32
32
  name="error"
33
33
  >
34
34
  <div
35
- class="flex h-[200px] items-center justify-center text-2xl text-error-400"
35
+ class="
36
+ text-error-400 flex h-[200px] items-center justify-center text-2xl
37
+ "
36
38
  >
37
39
  {{ StringHelper.getError(options.status.errorData) }}
38
40
  </div>
@@ -1,3 +1,3 @@
1
- {
2
- "extends": "../../../.nuxt/tsconfig.server.json",
3
- }
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }
@@ -1,6 +1,9 @@
1
1
  export declare const inputTheme: {
2
2
  slots: {
3
3
  root: string;
4
+ suggestionsContainer: string;
5
+ suggestionItem: string;
6
+ suggestionItemActive: string;
4
7
  };
5
8
  defaultVariants: {
6
9
  size: string;
@@ -1,6 +1,9 @@
1
1
  export const inputTheme = {
2
2
  slots: {
3
- root: "w-full"
3
+ root: "w-full",
4
+ suggestionsContainer: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow max-h-60 overflow-y-auto",
5
+ suggestionItem: "px-3 py-3 text-sm cursor-pointer hover:bg-(--ui-color-primary-100) truncate",
6
+ suggestionItemActive: "bg-(--ui-color-primary-100)"
4
7
  },
5
8
  defaultVariants: {
6
9
  size: "lg"