@konoma-development/vue-components 0.1.9 → 0.1.10

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.
@@ -1,9 +1,52 @@
1
1
  export default defineNuxtConfig({
2
2
  extends: [['..', { install: true }]],
3
- modules: ['@nuxt/eslint'],
3
+ modules: ['@nuxt/eslint', '@unocss/nuxt', '@nuxt/icon', '@vueuse/nuxt', 'floating-vue/nuxt'],
4
4
  eslint: {
5
5
  config: {
6
6
  standalone: false,
7
7
  },
8
8
  },
9
+ unocss: {
10
+ nuxtLayers: true,
11
+ },
12
+ typescript: {
13
+ typeCheck: true,
14
+ // Customize app/server TypeScript config
15
+ tsConfig: {
16
+ compilerOptions: {
17
+ moduleResolution: 'bundler',
18
+ noImplicitAny: true,
19
+ noImplicitOverride: false,
20
+ allowSyntheticDefaultImports: true,
21
+ esModuleInterop: true,
22
+ skipLibCheck: true,
23
+ },
24
+ exclude: ['node_modules/**', 'shared/openapi/**'],
25
+ },
26
+ // Customize build-time TypeScript config
27
+ nodeTsConfig: {
28
+ compilerOptions: {
29
+ moduleResolution: 'bundler',
30
+ noImplicitAny: true,
31
+ noImplicitOverride: false,
32
+ allowSyntheticDefaultImports: true,
33
+ esModuleInterop: true,
34
+ skipLibCheck: true,
35
+ },
36
+ exclude: ['node_modules/**', 'shared/openapi/**'],
37
+ },
38
+ },
39
+ vite: {
40
+ build: {
41
+ rollupOptions: {
42
+ external: ['@oxc-parser/binding-wasm32-wasi'],
43
+ },
44
+ },
45
+ optimizeDeps: {
46
+ include: [
47
+ '@vue/devtools-core',
48
+ '@vue/devtools-kit',
49
+ ],
50
+ },
51
+ },
9
52
  });
@@ -9,7 +9,7 @@
9
9
  <div
10
10
  v-for="column in currentColumnsLeft"
11
11
  :key="column.id"
12
- :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`"
12
+ :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`"
13
13
  :class="[
14
14
  headerClasses,
15
15
  hasFilters ? 'h-24' : 'h-12',
@@ -40,46 +40,46 @@
40
40
  </div>
41
41
  <div v-if="hasFilters" :key="Object.keys(filters).join('-')" class="bg-kvc-table-header text-xs text-secondary-500 font-medium min-h-10 w-full" @click.stop>
42
42
  <template v-if="column.filterable && column.filterKey">
43
- <component :is="column.filterComponent?.(filters, updateFilters) || filterComponents?.[column.id]?.(filters, updateFilters)" v-if="column.filterComponent?.(filters, updateFilters) || filterComponents?.[column.id]?.(filters, updateFilters)" />
44
- <KonomaInput
45
- v-else
46
- :key="filters[column.filterKey]?.join(', ')"
47
- :value="filters[column.filterKey]?.join(', ')"
48
- is-clearable
49
- class="h-10"
50
- icon-right-name="filters[column.filterKey] ? 'heroicons:x-mark' : ''"
51
- @key-down="async ($event) => {
52
- if ($event.key === 'Enter' && column.filterKey) {
53
- const key = column.filterKey;
54
- const value = ($event.currentTarget as HTMLInputElement).value;
55
- if ($event) {
56
- await updateFilters({ ...filters, [key]: [value] });
43
+ <slot v-if="$slots[getFilterSlotName(column.id as ColumnId)]" :filters="filters" :update-filters="updateFilters" :name="getFilterSlotName(column.id as ColumnId)">
44
+ <KonomaInput
45
+ :key="filters[column.filterKey]?.join(', ')"
46
+ :value="filters[column.filterKey]?.join(', ')"
47
+ is-clearable
48
+ class="h-10"
49
+ :icon-right-name="filters[column.filterKey] ? 'heroicons:x-mark' : ''"
50
+ @key-down="async ($event) => {
51
+ if ($event.key === 'Enter' && column.filterKey) {
52
+ const key = column.filterKey;
53
+ const value = ($event.currentTarget as HTMLInputElement).value;
54
+ if ($event) {
55
+ await updateFilters({ ...filters, [key]: [value] });
56
+ }
57
+ else {
58
+ const newFilters = { ...filters };
59
+ delete newFilters[key];
60
+ await updateFilters(newFilters);
61
+ }
57
62
  }
58
- else {
63
+ }"
64
+ @click.stop
65
+ @icon-right-click.stop="async ($event) => {
66
+ $event.stopPropagation();
67
+ const key = column.filterKey;
68
+ if (key) {
59
69
  const newFilters = { ...filters };
60
70
  delete newFilters[key];
61
71
  await updateFilters(newFilters);
62
72
  }
63
- }
64
- }"
65
- @click.stop
66
- @icon-right-click.stop="async ($event) => {
67
- $event.stopPropagation();
68
- const key = column.filterKey;
69
- if (key) {
70
- const newFilters = { ...filters };
71
- delete newFilters[key];
72
- await updateFilters(newFilters);
73
- }
74
- }"
75
- />
73
+ }"
74
+ />
75
+ </slot>
76
76
  </template>
77
77
  </div>
78
78
  </div>
79
79
  </div>
80
80
  <div
81
81
  v-for="column in currentColumnsCenter" :key="column.id"
82
- :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`"
82
+ :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`"
83
83
  :class="[
84
84
  headerClasses,
85
85
  hasFilters ? 'h-24' : 'h-12',
@@ -110,46 +110,46 @@
110
110
  </div>
111
111
  <div v-if="hasFilters" :key="Object.keys(filters).join('-')" class="bg-kvc-table-header text-xs text-secondary-500 font-medium min-h-10 w-full" @click.stop>
112
112
  <template v-if="column.filterable && column.filterKey">
113
- <component :is="column.filterComponent?.(filters, updateFilters) || filterComponents?.[column.id]?.(filters, updateFilters)" v-if="column.filterComponent?.(filters, updateFilters) || filterComponents?.[column.id]?.(filters, updateFilters)" />
114
- <KonomaInput
115
- v-else
116
- :key="filters[column.filterKey]?.join(', ')"
117
- :value="filters[column.filterKey]?.join(', ')"
118
- is-clearable
119
- class="h-10"
120
- icon-right-name="filters[column.filterKey] ? 'heroicons:x-mark' : ''"
121
- @key-down="async ($event) => {
122
- if ($event.key === 'Enter' && column.filterKey) {
123
- const key = column.filterKey;
124
- const value = ($event.currentTarget as HTMLInputElement).value;
125
- if ($event) {
126
- await updateFilters({ ...filters, [key]: [value] });
113
+ <slot v-if="$slots[getFilterSlotName(column.id as ColumnId)]" :filters="filters" :update-filters="updateFilters" :name="getFilterSlotName(column.id as ColumnId)">
114
+ <KonomaInput
115
+ :key="filters[column.filterKey]?.join(', ')"
116
+ :value="filters[column.filterKey]?.join(', ')"
117
+ is-clearable
118
+ class="h-10"
119
+ :icon-right-name="filters[column.filterKey] ? 'heroicons:x-mark' : ''"
120
+ @key-down="async ($event) => {
121
+ if ($event.key === 'Enter' && column.filterKey) {
122
+ const key = column.filterKey;
123
+ const value = ($event.currentTarget as HTMLInputElement).value;
124
+ if ($event) {
125
+ await updateFilters({ ...filters, [key]: [value] });
126
+ }
127
+ else {
128
+ const newFilters = { ...filters };
129
+ delete newFilters[key];
130
+ await updateFilters(newFilters);
131
+ }
127
132
  }
128
- else {
133
+ }"
134
+ @click.stop
135
+ @icon-right-click.stop="async ($event) => {
136
+ $event.stopPropagation();
137
+ const key = column.filterKey;
138
+ if (key) {
129
139
  const newFilters = { ...filters };
130
140
  delete newFilters[key];
131
141
  await updateFilters(newFilters);
132
142
  }
133
- }
134
- }"
135
- @click.stop
136
- @icon-right-click.stop="async ($event) => {
137
- $event.stopPropagation();
138
- const key = column.filterKey;
139
- if (key) {
140
- const newFilters = { ...filters };
141
- delete newFilters[key];
142
- await updateFilters(newFilters);
143
- }
144
- }"
145
- />
143
+ }"
144
+ />
145
+ </slot>
146
146
  </template>
147
147
  </div>
148
148
  </div>
149
149
  <div v-if="currentColumnsRight.length" class="flex flex-row h-full items-center right-0 sticky">
150
150
  <div
151
151
  v-for="column in currentColumnsRight"
152
- :key="column.id" :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`" class="bg-kvc-table-header text-xs font-medium px-4 py-3 flex flex-row h-full truncate items-start justify-end last:rounded-tr-kvc-table"
152
+ :key="column.id" :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`" class="bg-kvc-table-header text-xs font-medium px-4 py-3 flex flex-row h-full truncate items-start justify-end last:rounded-tr-kvc-table"
153
153
  >
154
154
  {{ column.title }}
155
155
  </div>
@@ -164,49 +164,43 @@
164
164
  <div
165
165
  v-for="column in currentColumnsRight"
166
166
  :key="column.id"
167
- :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`"
167
+ :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`"
168
168
  :class="[rowLeftWrapperClasses, column.grow ? 'grow' : ''].join(' ')"
169
169
  :title="(entry[column.id] as string) || ''"
170
170
  >
171
- <component
172
- :is="cellRenderer?.[column.id]?.(entry)"
173
- v-if="cellRenderer?.[column.id]?.(entry)"
174
- />
175
- <div v-else class="text-sm p-4 h-14 truncate">
176
- {{ (entry[column.id] as string) || '-' }}
177
- </div>
171
+ <slot :entry :name="getColumnSlotName(column.id as ColumnId)">
172
+ <div class="text-sm p-4 h-14 truncate">
173
+ {{ (entry[column.id] as string) || '-' }}
174
+ </div>
175
+ </slot>
178
176
  </div>
179
177
  </div>
180
178
  <div
181
179
  v-for="column in currentColumnsCenter"
182
180
  :key="column.id"
183
- :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`"
181
+ :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`"
184
182
  :class="[rowCenterWrapperClasses, column.grow ? 'grow' : ''].join(' ')"
185
183
  :title="(entry[column.id] as string) || ''"
186
184
  >
187
- <component
188
- :is="cellRenderer?.[column.id]?.(entry)"
189
- v-if="cellRenderer?.[column.id]?.(entry)"
190
- />
191
- <div v-else class="text-sm p-4 h-14 truncate">
192
- {{ (entry[column.id] as string) || '-' }}
193
- </div>
185
+ <slot :entry :name="getColumnSlotName(column.id as ColumnId)">
186
+ <div class="text-sm p-4 h-14 truncate">
187
+ {{ (entry[column.id] as string) || '-' }}
188
+ </div>
189
+ </slot>
194
190
  </div>
195
191
  <div v-if="currentColumnsRight.length" class="border-l flex flex-row right-0 sticky">
196
192
  <div
197
193
  v-for="column in currentColumnsRight"
198
194
  :key="column.id"
199
- :style="`minWidth: ${column.initialWidth}; maxWidth: ${!column.grow ? column.initialWidth : undefined};`"
195
+ :style="`min-width: ${column.initialWidth}; max-width: ${!column.grow ? column.initialWidth : undefined};`"
200
196
  :class="[rowRightWrapperClasses, column.grow ? 'grow' : ''].join(' ')"
201
197
  :title="(entry[column.id] as string) || ''"
202
198
  >
203
- <component
204
- :is="cellRenderer?.[column.id]?.(entry)"
205
- v-if="cellRenderer?.[column.id]?.(entry)"
206
- />
207
- <div v-else class="text-sm p-4 h-14 truncate">
208
- {{ (entry[column.id] as string) || '-' }}
209
- </div>
199
+ <slot :entry :name="getColumnSlotName(column.id as ColumnId)">
200
+ <div class="text-sm p-4 h-14 truncate">
201
+ {{ (entry[column.id] as string) || '-' }}
202
+ </div>
203
+ </slot>
210
204
  </div>
211
205
  </div>
212
206
  </div>
@@ -234,13 +228,20 @@
234
228
  </div>
235
229
  </template>
236
230
 
237
- <script lang="ts" setup generic="DataType extends { index?: number }">
231
+ <script lang="ts" setup generic="DataType">
238
232
  import type { PaginationClasses, TableColumn } from '../../types/table';
239
233
  import { baseClasses } from '../defaults/table';
240
234
  import KonomaInput from '../form/KonomaInput.vue';
241
235
  import KonomaIcon from '../ui/KonomaIcon.vue';
242
236
  import KonomaPagination from './KonomaPagination.vue';
243
237
 
238
+ type ColumnId = Extract<TableColumn<DataType>['id'], string>
239
+ type TableSlots = Record<`column-${ColumnId}`, (props: { entry: DataType }) => any>
240
+ & Record<`filter-${ColumnId}`, (props: {
241
+ filters: Record<string, string[]>
242
+ updateFilters: (filters: Record<string, string[]>) => Promise<void>
243
+ }) => any>
244
+
244
245
  const props = withDefaults(defineProps<{
245
246
  wrapperClasses?: string
246
247
  tableClasses?: string
@@ -260,15 +261,6 @@ const props = withDefaults(defineProps<{
260
261
  columnsCenter: TableColumn<DataType>[]
261
262
  columnsRight?: TableColumn<DataType>[]
262
263
  columnsLeft?: TableColumn<DataType>[]
263
- // TODO: Custom cell renderers
264
- cellRenderer?: { [key in keyof DataType]?: (data: DataType & { dragRef?: Ref }) => string }
265
- // TODO: Custom filter components
266
- filterComponents?: {
267
- [key in keyof DataType]?: (
268
- filters: Record<string, string[]>,
269
- setFilters: (filters: Record<string, string[]>) => Promise<void>,
270
- ) => string;
271
- }
272
264
  filters?: Record<string, string[]>
273
265
  showFilters?: boolean
274
266
  data: DataType[]
@@ -312,7 +304,17 @@ const emit = defineEmits<{
312
304
  (e: 'onUpdateColumnsRight', columns: TableColumn<DataType>[], updateMeta?: boolean): void
313
305
  }>()
314
306
 
315
- const locale = inject<string>('locale')
307
+ defineSlots<Partial<TableSlots>>()
308
+
309
+ function getColumnSlotName(columnId: ColumnId): `column-${ColumnId}` {
310
+ return `column-${columnId}`
311
+ }
312
+
313
+ function getFilterSlotName(columnId: ColumnId): `filter-${ColumnId}` {
314
+ return `filter-${columnId}`
315
+ }
316
+
317
+ const locale = inject<string>('locale', 'de')
316
318
 
317
319
  const hasFilters = computed(() => {
318
320
  return !!(
@@ -335,8 +337,6 @@ const currentEnd = computed(() => {
335
337
  return props.currentPage * props.pagesize;
336
338
  });
337
339
 
338
- // const headerRef = useTemplateRef('header');
339
-
340
340
  const currentColumnsLeft = computed(() => {
341
341
  return props.columnsLeft?.filter(column => !column?.hidden) || []
342
342
  });
@@ -11,28 +11,44 @@ const props = defineProps<{
11
11
  }>();
12
12
 
13
13
  const requiredThemeShades = [
14
- '50',
15
14
  '100',
16
- '150',
17
15
  '200',
18
- '250',
19
16
  '300',
20
- '350',
21
17
  '400',
22
- '450',
23
18
  '500',
24
- '550',
25
19
  '600',
26
- '650',
27
20
  '700',
28
- '750',
29
21
  '800',
30
- '850',
31
22
  '900',
32
- '950',
33
23
  'DEFAULT',
34
24
  ] as const;
35
25
 
26
+ const optionalThemeShades = [
27
+ '50',
28
+ '150',
29
+ '250',
30
+ '350',
31
+ '450',
32
+ '550',
33
+ '650',
34
+ '750',
35
+ '850',
36
+ '950',
37
+ ]
38
+
39
+ const optionalThemeShadesFallbacks: Record<typeof optionalThemeShades[number], typeof requiredThemeShades[number]> = {
40
+ 50: '100',
41
+ 150: '200',
42
+ 250: '300',
43
+ 350: '400',
44
+ 450: '500',
45
+ 550: '600',
46
+ 650: '700',
47
+ 750: '800',
48
+ 850: '900',
49
+ 950: '900',
50
+ }
51
+
36
52
  const appConfig = useAppConfig();
37
53
  const requiredThemeColors = Object.keys(appConfig['konoma-theme']);
38
54
 
@@ -64,6 +80,16 @@ async function initTheme(): Promise<Record<AppConfigThemeKey, Record<keyof typeo
64
80
 
65
81
  const unocssColorKeys = Object.keys(unocssColorObject);
66
82
  const missingShades = requiredThemeShades.filter(shade => !unocssColorKeys.includes(shade));
83
+
84
+ for (const optionalShade of optionalThemeShades) {
85
+ if (!unocssColorKeys.includes(optionalShade)) {
86
+ const fallbackShade = optionalThemeShadesFallbacks[optionalShade];
87
+ if (fallbackShade && unocssColorKeys.includes(fallbackShade)) {
88
+ (unocssColorObject as Record<string, string>)[optionalShade] = unocssColorObject[fallbackShade];
89
+ }
90
+ }
91
+ }
92
+
67
93
  if (missingShades.length) {
68
94
  throw new Error(
69
95
  `Theme color "${effectiveColorName}" is missing "${missingShades.join('", "')}" ${missingShades.length > 1 ? 'shades' : 'shade'}`,
package/eslint.config.ts CHANGED
@@ -18,6 +18,6 @@ export default withNuxt(
18
18
  order: [['template', 'script'], 'style'],
19
19
  }],
20
20
  },
21
- ignores: ['.nuxt/*', 'shared/openapi/*', 'node_modules/*'],
21
+ ignores: ['.nuxt/*', 'node_modules/*'],
22
22
  }),
23
23
  );
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "type": "module",
7
- "version": "0.1.9",
7
+ "version": "0.1.10",
8
8
  "packageManager": "yarn@4.14.1",
9
9
  "main": "./nuxt.config.ts",
10
10
  "scripts": {
@@ -29,10 +29,10 @@
29
29
  "@nuxt/devtools": "3.2.4",
30
30
  "@nuxt/eslint": "1.15.2",
31
31
  "@nuxt/icon": "2.2.1",
32
- "@nuxt/schema": "4.4.2",
32
+ "@nuxt/schema": "4.4.4",
33
33
  "@nuxt/test-utils": "4.0.3",
34
34
  "@pinia/nuxt": "0.11.3",
35
- "@sentry/nuxt": "10.50.0",
35
+ "@sentry/nuxt": "10.51.0",
36
36
  "@types/node": "22.19.17",
37
37
  "@unocss/eslint-plugin": "66.6.8",
38
38
  "@unocss/nuxt": "66.6.8",
@@ -43,7 +43,7 @@
43
43
  "changelogen": "0.6.2",
44
44
  "eslint": "10.2.1",
45
45
  "floating-vue": "5.2.2",
46
- "nuxt": "4.4.2",
46
+ "nuxt": "4.4.4",
47
47
  "nuxt-headlessui": "1.2.2",
48
48
  "pinia": "3.0.4",
49
49
  "tslib": "2.8.1",
package/types/table.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export interface TableColumn<DataType> {
2
2
  id: keyof DataType
3
3
  title?: string
4
- initialWidth?: string | number
4
+ initialWidth?: string
5
5
  hidden?: boolean
6
6
  sorting?: '+' | '-'
7
7
  // Mutually exclusive with onClick
@@ -13,8 +13,6 @@ export interface TableColumn<DataType> {
13
13
  allowResize?: boolean
14
14
  filterable?: boolean
15
15
  grow?: boolean
16
- // TODO: support custom filter component
17
- filterComponent?: (filters: Record<string, string[]>, setFilters: (filters: Record<string, string[]>) => Promise<void>) => string
18
16
  }
19
17
 
20
18
  export interface DragItem {