@shwfed/nuxt 0.10.10 → 0.10.12

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shwfed/nuxt",
3
3
  "configKey": "shwfed",
4
- "version": "0.10.10",
4
+ "version": "0.10.12",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -0,0 +1,29 @@
1
+ import { Effect } from 'effect';
2
+ import type { MenuTabsConfigInput } from './ui/menu-tabs/schema.js';
3
+ export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './ui/menu-tabs/schema.js';
4
+ export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './ui/menu-tabs/schema.js';
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
7
+ declare const __VLS_export: import("vue").DefineComponent<{
8
+ config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
9
+ context?: Record<string, unknown>;
10
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:config": (args_0: Readonly<{
12
+ menus: readonly Readonly<{
13
+ id: string;
14
+ title: import("../utils/coders.js").LocaleValue;
15
+ to: string;
16
+ }>[];
17
+ }>) => any;
18
+ }, string, import("vue").PublicProps, Readonly<{
19
+ config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
20
+ context?: Record<string, unknown>;
21
+ }> & Readonly<{
22
+ "onUpdate:config"?: ((args_0: Readonly<{
23
+ menus: readonly Readonly<{
24
+ id: string;
25
+ title: import("../utils/coders.js").LocaleValue;
26
+ to: string;
27
+ }>[];
28
+ }>) => any) | undefined;
29
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -0,0 +1,40 @@
1
+ <script setup>
2
+ import { Effect } from "effect";
3
+ import UiMenuTabs from "./ui/menu-tabs/MenuTabs.vue";
4
+ defineOptions({
5
+ inheritAttrs: false
6
+ });
7
+ const props = defineProps({
8
+ config: { type: null, required: false },
9
+ context: { type: Object, required: false }
10
+ });
11
+ const emit = defineEmits(["update:config"]);
12
+ const defaultConfig = {
13
+ menus: []
14
+ };
15
+ function isEffectConfig(value) {
16
+ return typeof value === "object" && value !== null && "pipe" in value && typeof Reflect.get(value, "pipe") === "function";
17
+ }
18
+ function resolveConfig() {
19
+ if (isEffectConfig(props.config)) {
20
+ return props.config.pipe(Effect.map((value) => value ?? defaultConfig));
21
+ }
22
+ return Effect.succeed(props.config ?? defaultConfig);
23
+ }
24
+ function handleConfigUpdate(config) {
25
+ emit("update:config", config);
26
+ }
27
+ </script>
28
+
29
+ <script>
30
+ export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from "./ui/menu-tabs/schema";
31
+ </script>
32
+
33
+ <template>
34
+ <UiMenuTabs
35
+ v-bind="$attrs"
36
+ :config="resolveConfig()"
37
+ :context="props.context"
38
+ @update:config="handleConfigUpdate"
39
+ />
40
+ </template>
@@ -0,0 +1,29 @@
1
+ import { Effect } from 'effect';
2
+ import type { MenuTabsConfigInput } from './ui/menu-tabs/schema.js';
3
+ export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './ui/menu-tabs/schema.js';
4
+ export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './ui/menu-tabs/schema.js';
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
7
+ declare const __VLS_export: import("vue").DefineComponent<{
8
+ config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
9
+ context?: Record<string, unknown>;
10
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:config": (args_0: Readonly<{
12
+ menus: readonly Readonly<{
13
+ id: string;
14
+ title: import("../utils/coders.js").LocaleValue;
15
+ to: string;
16
+ }>[];
17
+ }>) => any;
18
+ }, string, import("vue").PublicProps, Readonly<{
19
+ config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
20
+ context?: Record<string, unknown>;
21
+ }> & Readonly<{
22
+ "onUpdate:config"?: ((args_0: Readonly<{
23
+ menus: readonly Readonly<{
24
+ id: string;
25
+ title: import("../utils/coders.js").LocaleValue;
26
+ to: string;
27
+ }>[];
28
+ }>) => any) | undefined;
29
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -43,7 +43,7 @@ const open = defineModel("open", { type: Boolean, ...{
43
43
  const { $toast } = useNuxtApp();
44
44
  const { t } = useI18n();
45
45
  const search = ref("");
46
- const draftGap = ref(props.config.gap ?? 12);
46
+ const draftGap = ref(props.config.gap ?? 4);
47
47
  const draftStyle = ref(props.config.style);
48
48
  const selectedItemId = ref("general");
49
49
  const draftTree = ref(buildButtonConfiguratorTree(props.config.groups));
@@ -90,7 +90,6 @@ const selectedNode = computed(() => {
90
90
  const selectedGroup = computed(() => selectedNode.value?.item.type === "group" ? selectedNode.value.item : void 0);
91
91
  const selectedButton = computed(() => selectedNode.value?.item.type === "button" ? selectedNode.value.item : void 0);
92
92
  const selectedDropdown = computed(() => selectedNode.value?.item.type === "dropdown" ? selectedNode.value.item : void 0);
93
- const selectedNodeItemId = computed(() => selectedNode.value?.itemId ?? "");
94
93
  const selectedParentNodeId = computed(() => {
95
94
  const parentItemId = selectedNode.value?.parentItemId;
96
95
  if (!parentItemId) {
@@ -247,7 +246,7 @@ function getNodeLabel(item) {
247
246
  return getZhText(item.title) ?? t(item.type === "button" ? "untitled-button" : "untitled-dropdown");
248
247
  }
249
248
  function applyDraftConfig(config) {
250
- draftGap.value = config.gap ?? 12;
249
+ draftGap.value = config.gap ?? 4;
251
250
  draftStyle.value = config.style;
252
251
  draftTree.value = buildButtonConfiguratorTree(config.groups);
253
252
  validationErrors.value = {};
@@ -882,18 +881,7 @@ function confirmChanges() {
882
881
  v-else-if="selectedGroup"
883
882
  data-slot="button-configurator-group"
884
883
  class="mt-6 grid gap-4"
885
- >
886
- <Button
887
- type="button"
888
- variant="ghost"
889
- data-slot="button-configurator-delete-selected"
890
- class="justify-start text-red-600 hover:bg-red-50 hover:text-red-700"
891
- @click="deleteItem(selectedNodeItemId)"
892
- >
893
- <Icon icon="fluent:delete-20-regular" />
894
- {{ t("delete-group") }}
895
- </Button>
896
- </section>
884
+ />
897
885
 
898
886
  <section
899
887
  v-else-if="selectedButton"
@@ -980,17 +968,6 @@ function confirmChanges() {
980
968
  />
981
969
  <span class="text-sm text-zinc-700">{{ t("button-hide-title") }}</span>
982
970
  </label>
983
-
984
- <Button
985
- type="button"
986
- variant="ghost"
987
- data-slot="button-configurator-delete-selected"
988
- class="justify-start text-red-600 hover:bg-red-50 hover:text-red-700"
989
- @click="deleteItem(selectedNodeItemId)"
990
- >
991
- <Icon icon="fluent:delete-20-regular" />
992
- {{ t("delete-button") }}
993
- </Button>
994
971
  </section>
995
972
 
996
973
  <section
@@ -1032,17 +1009,6 @@ function confirmChanges() {
1032
1009
  </NativeSelectOption>
1033
1010
  </NativeSelect>
1034
1011
  </label>
1035
-
1036
- <Button
1037
- type="button"
1038
- variant="ghost"
1039
- data-slot="button-configurator-delete-selected"
1040
- class="justify-start text-red-600 hover:bg-red-50 hover:text-red-700"
1041
- @click="deleteItem(selectedNodeItemId)"
1042
- >
1043
- <Icon icon="fluent:delete-20-regular" />
1044
- {{ t("delete-dropdown") }}
1045
- </Button>
1046
1012
  </section>
1047
1013
 
1048
1014
  <section
@@ -1099,43 +1065,50 @@ function confirmChanges() {
1099
1065
  </section>
1100
1066
  </div>
1101
1067
 
1102
- <DialogFooter class="border-t border-zinc-200 px-6 py-4">
1103
- <Button
1104
- type="button"
1105
- variant="ghost"
1106
- data-slot="button-configurator-copy-markdown"
1107
- @click="void copyMarkdown()"
1108
- >
1109
- <Icon icon="fluent:document-one-page-20-regular" />
1110
- {{ t("copy-markdown") }}
1111
- </Button>
1112
- <Button
1113
- type="button"
1114
- variant="ghost"
1115
- data-slot="button-configurator-copy-config"
1116
- @click="void copyConfig()"
1117
- >
1118
- <Icon icon="fluent:code-text-20-regular" />
1119
- {{ t("copy-config") }}
1120
- </Button>
1121
- <Button
1122
- type="button"
1123
- variant="default"
1124
- data-slot="button-configurator-cancel"
1125
- @click="open = false"
1068
+ <DialogFooter class="border-t border-zinc-200 px-6 py-4 sm:justify-between">
1069
+ <div
1070
+ data-slot="button-configurator-copy-actions"
1071
+ class="flex items-center gap-2"
1126
1072
  >
1127
- <Icon icon="fluent:dismiss-20-regular" />
1128
- {{ t("cancel") }}
1129
- </Button>
1130
- <Button
1131
- type="button"
1132
- variant="primary"
1133
- data-slot="button-configurator-confirm"
1134
- @click="confirmChanges"
1135
- >
1136
- <Icon icon="fluent:checkmark-20-regular" />
1137
- {{ t("confirm") }}
1138
- </Button>
1073
+ <Button
1074
+ type="button"
1075
+ variant="ghost"
1076
+ data-slot="button-configurator-copy-markdown"
1077
+ @click="void copyMarkdown()"
1078
+ >
1079
+ <Icon icon="simple-icons:markdown" />
1080
+ {{ t("copy-markdown") }}
1081
+ </Button>
1082
+ <Button
1083
+ type="button"
1084
+ variant="ghost"
1085
+ data-slot="button-configurator-copy-config"
1086
+ @click="void copyConfig()"
1087
+ >
1088
+ <Icon icon="fluent:copy-20-regular" />
1089
+ {{ t("copy-config") }}
1090
+ </Button>
1091
+ </div>
1092
+ <div class="flex items-center gap-2">
1093
+ <Button
1094
+ type="button"
1095
+ variant="default"
1096
+ data-slot="button-configurator-cancel"
1097
+ @click="open = false"
1098
+ >
1099
+ <Icon icon="fluent:dismiss-20-regular" />
1100
+ {{ t("cancel") }}
1101
+ </Button>
1102
+ <Button
1103
+ type="button"
1104
+ variant="primary"
1105
+ data-slot="button-configurator-confirm"
1106
+ @click="confirmChanges"
1107
+ >
1108
+ <Icon icon="fluent:checkmark-20-regular" />
1109
+ {{ t("confirm") }}
1110
+ </Button>
1111
+ </div>
1139
1112
  </DialogFooter>
1140
1113
  </DialogContent>
1141
1114
  </Dialog>
@@ -23,7 +23,7 @@ const props = defineProps({
23
23
  });
24
24
  const emit = defineEmits(["update:config"]);
25
25
  const defaultConfig = {
26
- gap: 12,
26
+ gap: 4,
27
27
  groups: []
28
28
  };
29
29
  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
@@ -80,7 +80,7 @@ function getConfigStyle(config) {
80
80
  return style;
81
81
  }
82
82
  const rootStyle = computed(() => ({
83
- gap: `calc(${displayConfig.value.gap ?? defaultConfig.gap ?? 12} * 0.25rem)`,
83
+ gap: `calc(${displayConfig.value.gap ?? defaultConfig.gap ?? 4} * 0.25rem)`,
84
84
  ...getConfigStyle(displayConfig.value)
85
85
  }));
86
86
  function getButtonLabel(button) {
@@ -2,14 +2,15 @@
2
2
  import { icons } from "@iconify-json/fluent";
3
3
  import { Icon } from "@iconify/vue";
4
4
  import { useVirtualizer } from "@tanstack/vue-virtual";
5
- import { computed, shallowRef, ref, watch } from "vue";
5
+ import { computed, onBeforeUnmount, onMounted, shallowRef, ref, watch } from "vue";
6
6
  import { cn } from "../../../utils/cn";
7
7
  import { InputGroup, InputGroupAddon, InputGroupInput } from "../input-group";
8
8
  defineOptions({
9
9
  inheritAttrs: false
10
10
  });
11
- const ICON_COLUMNS = 6;
12
- const ICON_ROW_HEIGHT = 60;
11
+ const ICON_BUTTON_SIZE = 40;
12
+ const ICON_GRID_GAP = 8;
13
+ const ICON_GRID_MIN_COLUMN_WIDTH = 52;
13
14
  const props = defineProps({
14
15
  modelValue: { type: String, required: false },
15
16
  placeholder: { type: String, required: false },
@@ -25,6 +26,8 @@ const availableIcons = Object.entries(icons.icons).filter(([name]) => name.endsW
25
26
  const availableIconIds = new Set(availableIcons.map((icon) => icon.id));
26
27
  const searchQuery = ref("");
27
28
  const galleryElement = shallowRef(null);
29
+ const galleryWidth = ref(0);
30
+ let galleryResizeObserver;
28
31
  const selectedIcon = computed(() => props.modelValue && availableIconIds.has(props.modelValue) ? availableIcons.find((icon) => icon.id === props.modelValue) : void 0);
29
32
  const filteredIcons = computed(() => {
30
33
  const term = searchQuery.value.trim().toLowerCase();
@@ -33,11 +36,21 @@ const filteredIcons = computed(() => {
33
36
  }
34
37
  return availableIcons.filter((icon) => icon.id.includes(term));
35
38
  });
36
- const rowCount = computed(() => Math.ceil(filteredIcons.value.length / ICON_COLUMNS));
39
+ const iconColumns = computed(() => {
40
+ if (galleryWidth.value <= 0) {
41
+ return 8;
42
+ }
43
+ return Math.max(
44
+ 1,
45
+ Math.floor((galleryWidth.value + ICON_GRID_GAP) / (ICON_GRID_MIN_COLUMN_WIDTH + ICON_GRID_GAP))
46
+ );
47
+ });
48
+ const iconRowHeight = computed(() => ICON_BUTTON_SIZE + ICON_GRID_GAP);
49
+ const rowCount = computed(() => Math.ceil(filteredIcons.value.length / iconColumns.value));
37
50
  const rowVirtualizer = useVirtualizer(computed(() => ({
38
51
  count: rowCount.value,
39
52
  getScrollElement: () => galleryElement.value,
40
- estimateSize: () => ICON_ROW_HEIGHT,
53
+ estimateSize: () => iconRowHeight.value,
41
54
  overscan: 4
42
55
  })));
43
56
  watch(() => props.modelValue, (value) => {
@@ -45,9 +58,39 @@ watch(() => props.modelValue, (value) => {
45
58
  searchQuery.value = "";
46
59
  }
47
60
  });
61
+ watch(galleryElement, (element) => {
62
+ if (galleryResizeObserver) {
63
+ galleryResizeObserver.disconnect();
64
+ galleryResizeObserver = void 0;
65
+ }
66
+ if (!element || typeof ResizeObserver === "undefined") {
67
+ galleryWidth.value = 0;
68
+ return;
69
+ }
70
+ galleryWidth.value = element.clientWidth;
71
+ galleryResizeObserver = new ResizeObserver((entries) => {
72
+ const entry = entries[0];
73
+ if (!entry) {
74
+ return;
75
+ }
76
+ galleryWidth.value = entry.contentRect.width;
77
+ });
78
+ galleryResizeObserver.observe(element);
79
+ }, {
80
+ flush: "post"
81
+ });
82
+ onMounted(() => {
83
+ if (!galleryElement.value) {
84
+ return;
85
+ }
86
+ galleryWidth.value = galleryElement.value.clientWidth;
87
+ });
88
+ onBeforeUnmount(() => {
89
+ galleryResizeObserver?.disconnect();
90
+ });
48
91
  function iconsForRow(index) {
49
- const start = index * ICON_COLUMNS;
50
- return filteredIcons.value.slice(start, start + ICON_COLUMNS);
92
+ const start = index * iconColumns.value;
93
+ return filteredIcons.value.slice(start, start + iconColumns.value);
51
94
  }
52
95
  function selectIcon(iconId) {
53
96
  if (props.disabled) {
@@ -88,26 +131,28 @@ function handleSearchUpdate(value) {
88
131
  <div
89
132
  data-slot="icon-picker-hero"
90
133
  :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"
134
+ class="relative flex size-28 items-center justify-center rounded-2xl border border-zinc-200 text-zinc-700 shadow-xs"
92
135
  >
93
136
  <button
94
137
  type="button"
95
138
  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"
139
+ class="absolute right-2 top-2 flex size-7 items-center justify-center rounded-full border border-zinc-200 bg-white text-zinc-500 shadow-xs transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 disabled:pointer-events-none disabled:opacity-60"
97
140
  :disabled="props.disabled"
98
141
  @click="clearIcon"
99
142
  >
100
- <Icon icon="fluent:dismiss-20-regular" />
143
+ <Icon
144
+ icon="fluent:dismiss-20-regular"
145
+ class="size-4"
146
+ />
101
147
  </button>
102
148
  <Icon
103
149
  :icon="selectedIcon.icon"
104
- class="size-16"
150
+ class="size-14 shrink-0"
105
151
  />
106
152
  </div>
107
-
108
153
  <p
109
154
  data-slot="icon-picker-name"
110
- class="font-mono text-sm text-zinc-500"
155
+ class="max-w-full break-all font-mono text-sm text-zinc-500"
111
156
  >
112
157
  {{ selectedIcon.id }}
113
158
  </p>
@@ -144,9 +189,10 @@ function handleSearchUpdate(value) {
144
189
  <div
145
190
  v-for="row in rowVirtualizer.getVirtualItems()"
146
191
  :key="String(row.key)"
147
- class="absolute left-0 top-0 grid w-full grid-cols-6 gap-2"
192
+ class="absolute left-0 top-0 grid w-full gap-2"
148
193
  :style="{
149
194
  height: `${row.size}px`,
195
+ gridTemplateColumns: `repeat(${iconColumns}, minmax(0, 1fr))`,
150
196
  transform: `translateY(${row.start}px)`
151
197
  }"
152
198
  >
@@ -156,7 +202,7 @@ function handleSearchUpdate(value) {
156
202
  type="button"
157
203
  data-slot="icon-picker-item"
158
204
  :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"
205
+ class="mx-auto flex size-10 items-center justify-center rounded-md text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-800 disabled:pointer-events-none disabled:opacity-60"
160
206
  :disabled="props.disabled"
161
207
  @click="selectIcon(item.id)"
162
208
  >
@@ -0,0 +1,29 @@
1
+ import { Effect } from 'effect';
2
+ import { type MenuTabsConfigInput } from './schema.js';
3
+ export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './schema.js';
4
+ export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './schema.js';
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
7
+ declare const __VLS_export: import("vue").DefineComponent<{
8
+ config: Effect.Effect<MenuTabsConfigInput | undefined>;
9
+ context?: Record<string, unknown>;
10
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ "update:config": (args_0: Readonly<{
12
+ menus: readonly Readonly<{
13
+ id: string;
14
+ title: import("../../../utils/coders.js").LocaleValue;
15
+ to: string;
16
+ }>[];
17
+ }>) => any;
18
+ }, string, import("vue").PublicProps, Readonly<{
19
+ config: Effect.Effect<MenuTabsConfigInput | undefined>;
20
+ context?: Record<string, unknown>;
21
+ }> & Readonly<{
22
+ "onUpdate:config"?: ((args_0: Readonly<{
23
+ menus: readonly Readonly<{
24
+ id: string;
25
+ title: import("../../../utils/coders.js").LocaleValue;
26
+ to: string;
27
+ }>[];
28
+ }>) => any) | undefined;
29
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;