@shwfed/nuxt 0.7.10 → 0.8.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 (40) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +2 -1
  3. package/dist/runtime/components/table.d.vue.ts +68 -2
  4. package/dist/runtime/components/table.vue +0 -1
  5. package/dist/runtime/components/table.vue.d.ts +68 -2
  6. package/dist/runtime/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  7. package/dist/runtime/components/ui/field/FieldLabel.vue +1 -1
  8. package/dist/runtime/components/ui/fields/Fields.vue +14 -6
  9. package/dist/runtime/components/ui/fields-configurator/FieldsConfiguratorDialog.vue +28 -14
  10. package/dist/runtime/components/ui/icon-picker/IconPicker.d.vue.ts +15 -0
  11. package/dist/runtime/components/ui/icon-picker/IconPicker.vue +178 -0
  12. package/dist/runtime/components/ui/icon-picker/IconPicker.vue.d.ts +15 -0
  13. package/dist/runtime/components/ui/icon-picker/index.d.ts +1 -0
  14. package/dist/runtime/components/ui/icon-picker/index.js +1 -0
  15. package/dist/runtime/components/ui/input-group/InputGroupComboboxInput.vue +1 -1
  16. package/dist/runtime/components/ui/input-group/InputGroupInput.vue +1 -1
  17. package/dist/runtime/components/ui/input-group/InputGroupNumberField.vue +1 -1
  18. package/dist/runtime/components/ui/input-group/InputGroupTextarea.vue +1 -1
  19. package/dist/runtime/components/ui/native-select/NativeSelect.d.vue.ts +2 -2
  20. package/dist/runtime/components/ui/native-select/NativeSelect.vue +1 -1
  21. package/dist/runtime/components/ui/native-select/NativeSelect.vue.d.ts +2 -2
  22. package/dist/runtime/components/ui/switch/Switch.vue +2 -2
  23. package/dist/runtime/components/ui/table/Table.d.vue.ts +69 -3
  24. package/dist/runtime/components/ui/table/Table.vue +201 -41
  25. package/dist/runtime/components/ui/table/Table.vue.d.ts +69 -3
  26. package/dist/runtime/components/ui/table/schema.d.ts +107 -4
  27. package/dist/runtime/components/ui/table/schema.js +106 -90
  28. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.d.vue.ts +68 -2
  29. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue +732 -257
  30. package/dist/runtime/components/ui/table-configurator/TableConfiguratorDialog.vue.d.ts +68 -2
  31. package/dist/runtime/components/ui/textarea/Textarea.vue +1 -1
  32. package/dist/runtime/layouts/default.d.vue.ts +40 -0
  33. package/dist/runtime/layouts/default.vue +19 -0
  34. package/dist/runtime/layouts/default.vue.d.ts +40 -0
  35. package/dist/runtime/plugins/toast/index.d.ts +2 -2
  36. package/dist/runtime/table-renderers/builtins.js +151 -75
  37. package/dist/runtime/table-renderers/registry.d.ts +1 -1
  38. package/dist/runtime/utils/coders.d.ts +2 -0
  39. package/dist/runtime/utils/coders.js +13 -0
  40. package/package.json +6 -6
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shwfed/nuxt",
3
3
  "configKey": "shwfed",
4
- "version": "0.7.10",
4
+ "version": "0.8.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addImportsDir, addComponentsDir, addRouteMiddleware } from '@nuxt/kit';
2
+ import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addImportsDir, addComponentsDir, addRouteMiddleware, addLayout } from '@nuxt/kit';
3
3
  import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
4
4
  import TailwindCSS from '@tailwindcss/vite';
5
5
  import defu from 'defu';
@@ -75,6 +75,7 @@ const module$1 = defineNuxtModule({
75
75
  name: "token",
76
76
  global: true
77
77
  });
78
+ addLayout(resolver.resolve("runtime/layouts/default.vue"), "default");
78
79
  },
79
80
  hooks: {
80
81
  "build:error": (error) => {
@@ -35,9 +35,42 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
35
35
  grow?: boolean;
36
36
  }>[];
37
37
  cellStyles?: import("./table.vue.js").Expression;
38
- props?: Omit<import("@tanstack/table-core").TableOptions<unknown>, "columns" | "data" | "getRowId" | "getCoreRowModel">;
38
+ props?: Readonly<{
39
+ [key: string]: unknown;
40
+ initialState?: Readonly<{
41
+ columnVisibility?: Record<string, boolean>;
42
+ columnOrder?: ReadonlyArray<string>;
43
+ columnPinning?: Readonly<{
44
+ left?: ReadonlyArray<string>;
45
+ right?: ReadonlyArray<string>;
46
+ }>;
47
+ rowPinning?: Readonly<{
48
+ top?: ReadonlyArray<string>;
49
+ bottom?: ReadonlyArray<string>;
50
+ }>;
51
+ columnFilters?: ReadonlyArray<Readonly<{
52
+ id: string;
53
+ value: unknown;
54
+ }>>;
55
+ globalFilter?: unknown;
56
+ sorting?: ReadonlyArray<Readonly<{
57
+ id: string;
58
+ desc: boolean;
59
+ }>>;
60
+ expanded?: boolean | Record<string, boolean>;
61
+ grouping?: ReadonlyArray<string>;
62
+ columnSizing?: Record<string, number>;
63
+ columnSizingInfo?: Record<string, unknown>;
64
+ pagination?: Readonly<{
65
+ pageIndex?: number;
66
+ pageSize?: number;
67
+ }>;
68
+ rowSelection?: Record<string, boolean>;
69
+ }>;
70
+ }>;
39
71
  paginationLeft?: import("./table.vue.js").Markdown;
40
72
  paginationRight?: import("./table.vue.js").Markdown;
73
+ paginationPageSizes?: ReadonlyArray<number>;
41
74
  }>) => any;
42
75
  }, string, import("vue").PublicProps, Readonly<{
43
76
  config: import("effect").Effect.Effect<TableConfig>;
@@ -63,9 +96,42 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
63
96
  grow?: boolean;
64
97
  }>[];
65
98
  cellStyles?: import("./table.vue.js").Expression;
66
- props?: Omit<import("@tanstack/table-core").TableOptions<unknown>, "columns" | "data" | "getRowId" | "getCoreRowModel">;
99
+ props?: Readonly<{
100
+ [key: string]: unknown;
101
+ initialState?: Readonly<{
102
+ columnVisibility?: Record<string, boolean>;
103
+ columnOrder?: ReadonlyArray<string>;
104
+ columnPinning?: Readonly<{
105
+ left?: ReadonlyArray<string>;
106
+ right?: ReadonlyArray<string>;
107
+ }>;
108
+ rowPinning?: Readonly<{
109
+ top?: ReadonlyArray<string>;
110
+ bottom?: ReadonlyArray<string>;
111
+ }>;
112
+ columnFilters?: ReadonlyArray<Readonly<{
113
+ id: string;
114
+ value: unknown;
115
+ }>>;
116
+ globalFilter?: unknown;
117
+ sorting?: ReadonlyArray<Readonly<{
118
+ id: string;
119
+ desc: boolean;
120
+ }>>;
121
+ expanded?: boolean | Record<string, boolean>;
122
+ grouping?: ReadonlyArray<string>;
123
+ columnSizing?: Record<string, number>;
124
+ columnSizingInfo?: Record<string, unknown>;
125
+ pagination?: Readonly<{
126
+ pageIndex?: number;
127
+ pageSize?: number;
128
+ }>;
129
+ rowSelection?: Record<string, boolean>;
130
+ }>;
131
+ }>;
67
132
  paginationLeft?: import("./table.vue.js").Markdown;
68
133
  paginationRight?: import("./table.vue.js").Markdown;
134
+ paginationPageSizes?: ReadonlyArray<number>;
69
135
  }>) => any) | undefined;
70
136
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
71
137
  [x: string]: ((props: {
@@ -49,7 +49,6 @@ export { AccessorC, ColumnC, RenderC, TableConfigC } from "./ui/table/schema";
49
49
  <template>
50
50
  <UiTable
51
51
  ref="tableRef"
52
- v-bind="$attrs"
53
52
  :config="props.config"
54
53
  :data="props.data"
55
54
  :row-count="props.rowCount"
@@ -35,9 +35,42 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
35
35
  grow?: boolean;
36
36
  }>[];
37
37
  cellStyles?: import("./table.vue.js").Expression;
38
- props?: Omit<import("@tanstack/table-core").TableOptions<unknown>, "columns" | "data" | "getRowId" | "getCoreRowModel">;
38
+ props?: Readonly<{
39
+ [key: string]: unknown;
40
+ initialState?: Readonly<{
41
+ columnVisibility?: Record<string, boolean>;
42
+ columnOrder?: ReadonlyArray<string>;
43
+ columnPinning?: Readonly<{
44
+ left?: ReadonlyArray<string>;
45
+ right?: ReadonlyArray<string>;
46
+ }>;
47
+ rowPinning?: Readonly<{
48
+ top?: ReadonlyArray<string>;
49
+ bottom?: ReadonlyArray<string>;
50
+ }>;
51
+ columnFilters?: ReadonlyArray<Readonly<{
52
+ id: string;
53
+ value: unknown;
54
+ }>>;
55
+ globalFilter?: unknown;
56
+ sorting?: ReadonlyArray<Readonly<{
57
+ id: string;
58
+ desc: boolean;
59
+ }>>;
60
+ expanded?: boolean | Record<string, boolean>;
61
+ grouping?: ReadonlyArray<string>;
62
+ columnSizing?: Record<string, number>;
63
+ columnSizingInfo?: Record<string, unknown>;
64
+ pagination?: Readonly<{
65
+ pageIndex?: number;
66
+ pageSize?: number;
67
+ }>;
68
+ rowSelection?: Record<string, boolean>;
69
+ }>;
70
+ }>;
39
71
  paginationLeft?: import("./table.vue.js").Markdown;
40
72
  paginationRight?: import("./table.vue.js").Markdown;
73
+ paginationPageSizes?: ReadonlyArray<number>;
41
74
  }>) => any;
42
75
  }, string, import("vue").PublicProps, Readonly<{
43
76
  config: import("effect").Effect.Effect<TableConfig>;
@@ -63,9 +96,42 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
63
96
  grow?: boolean;
64
97
  }>[];
65
98
  cellStyles?: import("./table.vue.js").Expression;
66
- props?: Omit<import("@tanstack/table-core").TableOptions<unknown>, "columns" | "data" | "getRowId" | "getCoreRowModel">;
99
+ props?: Readonly<{
100
+ [key: string]: unknown;
101
+ initialState?: Readonly<{
102
+ columnVisibility?: Record<string, boolean>;
103
+ columnOrder?: ReadonlyArray<string>;
104
+ columnPinning?: Readonly<{
105
+ left?: ReadonlyArray<string>;
106
+ right?: ReadonlyArray<string>;
107
+ }>;
108
+ rowPinning?: Readonly<{
109
+ top?: ReadonlyArray<string>;
110
+ bottom?: ReadonlyArray<string>;
111
+ }>;
112
+ columnFilters?: ReadonlyArray<Readonly<{
113
+ id: string;
114
+ value: unknown;
115
+ }>>;
116
+ globalFilter?: unknown;
117
+ sorting?: ReadonlyArray<Readonly<{
118
+ id: string;
119
+ desc: boolean;
120
+ }>>;
121
+ expanded?: boolean | Record<string, boolean>;
122
+ grouping?: ReadonlyArray<string>;
123
+ columnSizing?: Record<string, number>;
124
+ columnSizingInfo?: Record<string, unknown>;
125
+ pagination?: Readonly<{
126
+ pageIndex?: number;
127
+ pageSize?: number;
128
+ }>;
129
+ rowSelection?: Record<string, boolean>;
130
+ }>;
131
+ }>;
67
132
  paginationLeft?: import("./table.vue.js").Markdown;
68
133
  paginationRight?: import("./table.vue.js").Markdown;
134
+ paginationPageSizes?: ReadonlyArray<number>;
69
135
  }>) => any) | undefined;
70
136
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
71
137
  [x: string]: ((props: {
@@ -22,7 +22,7 @@ const forwardedProps = useForwardPropsEmits(delegatedProps, emits);
22
22
  :data-inset="inset ? '' : void 0"
23
23
  :data-variant="variant"
24
24
  v-bind="forwardedProps"
25
- :class="cn('focus:bg-[color-mix(in_srgb,var(--primary)_10%,white)] hover:bg-[color-mix(in_srgb,var(--primary)_10%,white)] focus:text-(--primary) hover:text-(--primary) data-[variant=destructive]:text-red-600 data-[variant=destructive]:focus:bg-red-200 dark:data-[variant=destructive]:focus:bg-red-800 data-[variant=destructive]:focus:text-red-600 data-[variant=destructive]:*:[svg]:text-red-600! [&_svg:not([class*=\'text-\'])]:text-zinc-300 relative flex cursor-pointer items-center gap-2 rounded-sm p-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class)"
25
+ :class="cn('focus:bg-[color-mix(in_srgb,var(--primary)_10%,white)] hover:bg-[color-mix(in_srgb,var(--primary)_10%,white)] focus:text-(--primary) hover:text-(--primary) data-[variant=destructive]:text-red-600 data-[variant=destructive]:focus:bg-red-200 data-[variant=destructive]:focus:text-red-600 data-[variant=destructive]:*:[svg]:text-red-600! [&_svg:not([class*=\'text-\'])]:text-zinc-300 relative flex cursor-pointer items-center gap-2 rounded-sm p-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class)"
26
26
  >
27
27
  <slot />
28
28
  </DropdownMenuItem>
@@ -12,7 +12,7 @@ const props = defineProps({
12
12
  :class="cn(
13
13
  'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:text-zinc-600 group-data-[disabled=true]/field:opacity-100',
14
14
  'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border *:data-[slot=field]:p-4',
15
- 'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
15
+ 'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary',
16
16
  props.class
17
17
  )"
18
18
  >
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import { Icon } from "@iconify/vue";
3
3
  import { useNuxtApp } from "#app";
4
- import { ref, toRaw, useId, watchEffect } from "vue";
4
+ import { ref, toRaw, useId, watch, watchEffect } from "vue";
5
5
  import { Field, FieldContent, FieldError, FieldLabel } from "../field";
6
6
  import { Button } from "../button";
7
7
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupCombobox, InputGroupInput, InputGroupNumberField } from "../input-group";
@@ -16,7 +16,7 @@ import { Skeleton } from "../skeleton";
16
16
  import z from "zod";
17
17
  import { Effect } from "effect";
18
18
  import { computedAsync } from "@vueuse/core";
19
- import { localeC, dotPropC, expressionC } from "../../../utils/coders";
19
+ import { getLocalizedText, localeC, dotPropC, expressionC } from "../../../utils/coders";
20
20
  import { CalendarDate, getLocalTimeZone } from "@internationalized/date";
21
21
  import { format, parse } from "date-fns";
22
22
  import { TZDate } from "@date-fns/tz";
@@ -102,6 +102,7 @@ const modelValue = defineModel("modelValue", { type: Object, ...{
102
102
  } });
103
103
  const isCheating = useCheating();
104
104
  const isConfiguratorOpen = ref(false);
105
+ const displayFields = ref([]);
105
106
  const validationErrors = ref({});
106
107
  const calendarOpen = ref({});
107
108
  function toCalendarDateValue(value, valueFormat) {
@@ -159,6 +160,9 @@ function validateField(field) {
159
160
  function isFieldInvalid(field) {
160
161
  return validationErrors.value[field.path] !== void 0;
161
162
  }
163
+ function getFieldLabel(field) {
164
+ return getLocalizedText(field.title, locale.value) ?? field.path;
165
+ }
162
166
  function renderValidationMessage(field) {
163
167
  const error = validationErrors.value[field.path];
164
168
  if (!error) return "";
@@ -180,11 +184,15 @@ function handleCalendarBlur(field) {
180
184
  }, 0);
181
185
  }
182
186
  function handleConfiguratorConfirm(nextFields) {
187
+ displayFields.value = nextFields.slice();
183
188
  emit("update:fields", nextFields);
184
189
  }
190
+ watch(fields, (value) => {
191
+ displayFields.value = (value ?? []).slice();
192
+ }, { immediate: true });
185
193
  watchEffect(() => {
186
194
  const activePaths = /* @__PURE__ */ new Set();
187
- for (const field of fields.value ?? []) {
195
+ for (const field of displayFields.value) {
188
196
  if (!isFieldHidden(field) && !isFieldDisabled(field)) {
189
197
  activePaths.add(field.path);
190
198
  }
@@ -224,7 +232,7 @@ watchEffect(() => {
224
232
  <FieldsConfiguratorDialog
225
233
  v-if="fields"
226
234
  v-model:open="isConfiguratorOpen"
227
- :fields="fields"
235
+ :fields="displayFields"
228
236
  @confirm="handleConfiguratorConfirm"
229
237
  />
230
238
 
@@ -234,7 +242,7 @@ watchEffect(() => {
234
242
  />
235
243
 
236
244
  <template
237
- v-for="field in fields"
245
+ v-for="field in displayFields"
238
246
  :key="field.path"
239
247
  >
240
248
  <Field
@@ -245,7 +253,7 @@ watchEffect(() => {
245
253
  :style="field.style ? $dsl.evaluate`${field.style}`() : {}"
246
254
  >
247
255
  <FieldLabel :for="['string', 'number'].includes(field.type) ? `${id}:${field.path}` : void 0">
248
- {{ field.title.find((r) => r.locale === locale)?.message ?? field.title[0]?.message }}
256
+ {{ getFieldLabel(field) }}
249
257
  <span v-if="isCheating">
250
258
  <span class="font-mono">{{ field.path }}</span>
251
259
  </span>
@@ -4,6 +4,7 @@ import { Icon } from "@iconify/vue";
4
4
  import { computed, nextTick, ref, watch } from "vue";
5
5
  import { useI18n } from "vue-i18n";
6
6
  import { cn } from "../../../utils/cn";
7
+ import { getLocalizedText } from "../../../utils/coders";
7
8
  import { Button } from "../button";
8
9
  import {
9
10
  Dialog,
@@ -13,6 +14,7 @@ import {
13
14
  DialogHeader,
14
15
  DialogTitle
15
16
  } from "../dialog";
17
+ import Locale from "../locale/Locale.vue";
16
18
  const props = defineProps({
17
19
  fields: { type: Array, required: true }
18
20
  });
@@ -30,8 +32,7 @@ const generalItem = computed(() => ({
30
32
  label: t("general")
31
33
  }));
32
34
  function getFieldLabel(field) {
33
- const localizedTitle = field.title.find((item) => item.locale === locale.value && item.message.trim().length > 0);
34
- return localizedTitle?.message ?? field.path;
35
+ return getLocalizedText(field.title, locale.value) ?? field.path;
35
36
  }
36
37
  const fieldItems = computed(() => draftFields.value.map((field) => ({
37
38
  itemId: field.path,
@@ -144,6 +145,12 @@ function deleteField(itemId) {
144
145
  const nextField = nextFields[deleteIndex] ?? nextFields[deleteIndex - 1];
145
146
  selectedItemId.value = nextField?.path ?? "general";
146
147
  }
148
+ function updateSelectedFieldTitle(value) {
149
+ if (!selectedField.value) {
150
+ return;
151
+ }
152
+ draftFields.value = draftFields.value.map((field) => field.path === selectedField.value?.path ? { ...field, title: value } : field);
153
+ }
147
154
  function confirmChanges() {
148
155
  emit("confirm", draftFields.value.slice());
149
156
  open.value = false;
@@ -230,12 +237,6 @@ function confirmChanges() {
230
237
  >
231
238
  {{ item.label }}
232
239
  </span>
233
- <span
234
- data-slot="fields-configurator-field-path"
235
- class="block truncate text-xs text-zinc-400"
236
- >
237
- {{ item.path }}
238
- </span>
239
240
  </button>
240
241
 
241
242
  <button
@@ -286,10 +287,23 @@ function confirmChanges() {
286
287
 
287
288
  <div
288
289
  v-else-if="selectedField"
289
- data-slot="fields-configurator-field-placeholder"
290
- class="mt-6 flex min-h-48 items-center justify-center rounded-lg border border-dashed border-zinc-200 bg-zinc-50/60 px-6 text-center text-sm text-zinc-400"
290
+ data-slot="fields-configurator-field-main"
291
+ class="mt-6 flex flex-col gap-6"
291
292
  >
292
- {{ t("field-placeholder", { field: selectedItemLabel }) }}
293
+ <section
294
+ data-slot="fields-configurator-field-label-section"
295
+ class="flex flex-col gap-2"
296
+ >
297
+ <p class="text-xs font-medium text-zinc-500">
298
+ {{ t("field-label") }}
299
+ </p>
300
+
301
+ <Locale
302
+ data-slot="fields-configurator-field-title-locale"
303
+ :model-value="selectedField.title"
304
+ @update:model-value="updateSelectedFieldTitle"
305
+ />
306
+ </section>
293
307
  </div>
294
308
  </section>
295
309
  </div>
@@ -326,7 +340,7 @@ function confirmChanges() {
326
340
  "general": "通用",
327
341
  "general-placeholder": "这里会放置字段集合的通用配置。",
328
342
  "general-empty": "通用配置区域预留中。",
329
- "field-placeholder": "字段“{field}”的配置区域预留中。",
343
+ "field-label": "Label",
330
344
  "no-fields": "还没有字段。",
331
345
  "drag-field": "拖拽调整字段顺序:{field}",
332
346
  "delete-field": "删除字段:{field}",
@@ -339,7 +353,7 @@ function confirmChanges() {
339
353
  "general": "共通",
340
354
  "general-placeholder": "ここにはフィールド群の共通設定を配置します。",
341
355
  "general-empty": "共通設定エリアはまだ準備中です。",
342
- "field-placeholder": "フィールド「{field}」の設定エリアはまだ準備中です。",
356
+ "field-label": "Label",
343
357
  "no-fields": "フィールドがありません。",
344
358
  "drag-field": "{field} の順序をドラッグで変更",
345
359
  "delete-field": "{field} を削除",
@@ -352,7 +366,7 @@ function confirmChanges() {
352
366
  "general": "General",
353
367
  "general-placeholder": "Shared settings for this field group will live here.",
354
368
  "general-empty": "The shared settings area is reserved for now.",
355
- "field-placeholder": "The configuration area for \"{field}\" is reserved for now.",
369
+ "field-label": "Label",
356
370
  "no-fields": "No fields yet.",
357
371
  "drag-field": "Drag to reorder field {field}",
358
372
  "delete-field": "Delete field {field}",
@@ -0,0 +1,15 @@
1
+ import type { HTMLAttributes } from 'vue';
2
+ type __VLS_Props = {
3
+ modelValue?: string;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ invalid?: boolean;
7
+ class?: HTMLAttributes['class'];
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ "update:modelValue": (args_0: string | undefined) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:modelValue"?: ((args_0: string | undefined) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
@@ -0,0 +1,178 @@
1
+ <script setup>
2
+ import { icons } from "@iconify-json/fluent";
3
+ import { Icon } from "@iconify/vue";
4
+ import { useVirtualizer } from "@tanstack/vue-virtual";
5
+ import { computed, shallowRef, ref, watch } from "vue";
6
+ import { cn } from "../../../utils/cn";
7
+ import { InputGroup, InputGroupAddon, InputGroupInput } from "../input-group";
8
+ defineOptions({
9
+ inheritAttrs: false
10
+ });
11
+ const ICON_COLUMNS = 6;
12
+ const ICON_ROW_HEIGHT = 60;
13
+ const props = defineProps({
14
+ modelValue: { type: String, required: false },
15
+ placeholder: { type: String, required: false },
16
+ disabled: { type: Boolean, required: false },
17
+ invalid: { type: Boolean, required: false },
18
+ class: { type: null, required: false }
19
+ });
20
+ const emit = defineEmits(["update:modelValue"]);
21
+ const availableIcons = Object.entries(icons.icons).filter(([name]) => name.endsWith("-20-regular")).map(([name, icon]) => ({
22
+ id: `fluent:${name}`,
23
+ icon
24
+ }));
25
+ const availableIconIds = new Set(availableIcons.map((icon) => icon.id));
26
+ const searchQuery = ref("");
27
+ const galleryElement = shallowRef(null);
28
+ const selectedIcon = computed(() => props.modelValue && availableIconIds.has(props.modelValue) ? availableIcons.find((icon) => icon.id === props.modelValue) : void 0);
29
+ const filteredIcons = computed(() => {
30
+ const term = searchQuery.value.trim().toLowerCase();
31
+ if (!term) {
32
+ return availableIcons;
33
+ }
34
+ return availableIcons.filter((icon) => icon.id.includes(term));
35
+ });
36
+ const rowCount = computed(() => Math.ceil(filteredIcons.value.length / ICON_COLUMNS));
37
+ const rowVirtualizer = useVirtualizer(computed(() => ({
38
+ count: rowCount.value,
39
+ getScrollElement: () => galleryElement.value,
40
+ estimateSize: () => ICON_ROW_HEIGHT,
41
+ overscan: 4
42
+ })));
43
+ watch(() => props.modelValue, (value) => {
44
+ if (value && availableIconIds.has(value)) {
45
+ searchQuery.value = "";
46
+ }
47
+ });
48
+ function iconsForRow(index) {
49
+ const start = index * ICON_COLUMNS;
50
+ return filteredIcons.value.slice(start, start + ICON_COLUMNS);
51
+ }
52
+ function selectIcon(iconId) {
53
+ if (props.disabled) {
54
+ return;
55
+ }
56
+ searchQuery.value = "";
57
+ emit("update:modelValue", iconId);
58
+ }
59
+ function clearIcon() {
60
+ if (props.disabled) {
61
+ return;
62
+ }
63
+ searchQuery.value = "";
64
+ emit("update:modelValue", void 0);
65
+ }
66
+ function handleSearchUpdate(value) {
67
+ searchQuery.value = String(value);
68
+ }
69
+ </script>
70
+
71
+ <template>
72
+ <div
73
+ v-bind="$attrs"
74
+ data-slot="icon-picker"
75
+ :data-disabled="props.disabled ? 'true' : void 0"
76
+ :class="cn(
77
+ 'flex min-h-88 flex-col overflow-hidden rounded-xl border bg-white',
78
+ props.invalid ? 'border-red-400' : 'border-zinc-200',
79
+ props.disabled ? 'bg-zinc-50' : void 0,
80
+ props.class
81
+ )"
82
+ >
83
+ <section
84
+ v-if="selectedIcon"
85
+ data-slot="icon-picker-selected"
86
+ class="flex flex-1 flex-col items-center justify-center gap-4 px-6 py-8 text-center"
87
+ >
88
+ <div
89
+ data-slot="icon-picker-hero"
90
+ :data-icon-id="selectedIcon.id"
91
+ class="relative flex size-40 items-center justify-center rounded-3xl border border-zinc-200 bg-zinc-50 text-zinc-700 shadow-xs"
92
+ >
93
+ <button
94
+ type="button"
95
+ data-slot="icon-picker-clear"
96
+ class="absolute right-3 top-3 flex size-8 items-center justify-center rounded-full border border-zinc-200 bg-white text-zinc-500 transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 disabled:pointer-events-none disabled:opacity-60"
97
+ :disabled="props.disabled"
98
+ @click="clearIcon"
99
+ >
100
+ <Icon icon="fluent:dismiss-20-regular" />
101
+ </button>
102
+ <Icon
103
+ :icon="selectedIcon.icon"
104
+ class="size-16"
105
+ />
106
+ </div>
107
+
108
+ <p
109
+ data-slot="icon-picker-name"
110
+ class="font-mono text-sm text-zinc-500"
111
+ >
112
+ {{ selectedIcon.id }}
113
+ </p>
114
+ </section>
115
+
116
+ <template v-else>
117
+ <div
118
+ data-slot="icon-picker-search"
119
+ class="border-b border-zinc-200 p-4"
120
+ >
121
+ <InputGroup class="overflow-hidden">
122
+ <InputGroupAddon>
123
+ <Icon icon="fluent:search-20-regular" />
124
+ </InputGroupAddon>
125
+ <InputGroupInput
126
+ :model-value="searchQuery"
127
+ :placeholder="props.placeholder ?? 'Search icons'"
128
+ :disabled="props.disabled"
129
+ @update:model-value="handleSearchUpdate"
130
+ />
131
+ </InputGroup>
132
+ </div>
133
+
134
+ <div
135
+ v-if="filteredIcons.length > 0"
136
+ ref="galleryElement"
137
+ data-slot="icon-picker-gallery"
138
+ class="h-80 overflow-auto px-4 py-4"
139
+ >
140
+ <div
141
+ class="relative w-full"
142
+ :style="{ height: `${rowVirtualizer.getTotalSize()}px` }"
143
+ >
144
+ <div
145
+ v-for="row in rowVirtualizer.getVirtualItems()"
146
+ :key="String(row.key)"
147
+ class="absolute left-0 top-0 grid w-full grid-cols-6 gap-2"
148
+ :style="{
149
+ height: `${row.size}px`,
150
+ transform: `translateY(${row.start}px)`
151
+ }"
152
+ >
153
+ <button
154
+ v-for="item in iconsForRow(row.index)"
155
+ :key="item.id"
156
+ type="button"
157
+ data-slot="icon-picker-item"
158
+ :data-icon-id="item.id"
159
+ class="flex h-12 items-center justify-center rounded-lg border border-transparent bg-zinc-50 text-zinc-600 transition-colors hover:border-zinc-200 hover:bg-zinc-100 hover:text-zinc-800 disabled:pointer-events-none disabled:opacity-60"
160
+ :disabled="props.disabled"
161
+ @click="selectIcon(item.id)"
162
+ >
163
+ <Icon :icon="item.icon" />
164
+ </button>
165
+ </div>
166
+ </div>
167
+ </div>
168
+
169
+ <div
170
+ v-else
171
+ data-slot="icon-picker-empty"
172
+ class="flex h-80 items-center justify-center px-6 text-center text-sm text-zinc-400"
173
+ >
174
+ No icons match your search.
175
+ </div>
176
+ </template>
177
+ </div>
178
+ </template>
@@ -0,0 +1,15 @@
1
+ import type { HTMLAttributes } from 'vue';
2
+ type __VLS_Props = {
3
+ modelValue?: string;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ invalid?: boolean;
7
+ class?: HTMLAttributes['class'];
8
+ };
9
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ "update:modelValue": (args_0: string | undefined) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ "onUpdate:modelValue"?: ((args_0: string | undefined) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
@@ -0,0 +1 @@
1
+ export { default as IconPicker } from './IconPicker.vue.js';
@@ -0,0 +1 @@
1
+ export { default as IconPicker } from "./IconPicker.vue";
@@ -20,7 +20,7 @@ function handleBlur(event) {
20
20
  :data-value="!!filterState.search"
21
21
  :aria-invalid="props.invalid ? 'true' : void 0"
22
22
  :class="cn(
23
- 'flex-1 px-2 rounded-none border-none bg-transparent shadow-none dark:bg-transparent peer outline-none disabled:cursor-not-allowed disabled:text-zinc-600 disabled:opacity-100'
23
+ 'flex-1 px-2 rounded-none border-none bg-transparent shadow-none peer outline-none disabled:cursor-not-allowed disabled:text-zinc-600 disabled:opacity-100'
24
24
  )"
25
25
  @blur="handleBlur"
26
26
  />
@@ -13,7 +13,7 @@ const props = defineProps({
13
13
  data-slot="input-group-control"
14
14
  :treat-empty-as-different-state-from-null="treatEmptyAsDifferentStateFromNull"
15
15
  :class="cn(
16
- 'flex-1 rounded-none border-0 bg-transparent shadow-none dark:bg-transparent peer disabled:text-zinc-600 disabled:opacity-100',
16
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none peer disabled:text-zinc-600 disabled:opacity-100',
17
17
  props.class
18
18
  )"
19
19
  />
@@ -43,7 +43,7 @@ const delegatedProps = reactiveOmit(props, "class", "invalid");
43
43
  :data-value="typeof delegatedProps.modelValue === 'number'"
44
44
  :aria-invalid="props.invalid ? 'true' : void 0"
45
45
  :class="cn(
46
- 'flex-1 px-2 py-1 rounded-none w-full text-sm text-zinc-700 border-0 bg-transparent shadow-none dark:bg-transparent outline-none peer disabled:cursor-not-allowed disabled:text-zinc-600 disabled:opacity-100'
46
+ 'flex-1 px-2 py-1 rounded-none w-full text-sm text-zinc-700 border-0 bg-transparent shadow-none outline-none peer disabled:cursor-not-allowed disabled:text-zinc-600 disabled:opacity-100'
47
47
  )"
48
48
  @blur="emits('blur', $event)"
49
49
  />