minm-ui 1.0.0 → 1.0.1

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 (48) hide show
  1. package/README.md +29 -0
  2. package/dist/components/UiAreaChart.vue.d.ts +14 -0
  3. package/dist/components/UiBadge.vue.d.ts +19 -0
  4. package/dist/components/UiBarChart.vue.d.ts +14 -0
  5. package/dist/components/UiButton.vue.d.ts +27 -0
  6. package/dist/components/UiCard.vue.d.ts +24 -0
  7. package/dist/components/UiDonutChart.vue.d.ts +18 -0
  8. package/dist/components/UiDrawer.vue.d.ts +25 -0
  9. package/dist/components/UiEmptyState.vue.d.ts +18 -0
  10. package/dist/components/UiIcon.vue.d.ts +9 -0
  11. package/dist/components/UiInput.vue.d.ts +18 -0
  12. package/dist/components/UiModal.vue.d.ts +24 -0
  13. package/dist/components/UiPagination.vue.d.ts +14 -0
  14. package/dist/components/UiSelect.vue.d.ts +21 -0
  15. package/dist/components/UiSkeleton.vue.d.ts +10 -0
  16. package/dist/components/UiSpinner.vue.d.ts +9 -0
  17. package/dist/components/UiStatTile.vue.d.ts +12 -0
  18. package/dist/components/UiTable.vue.d.ts +38 -0
  19. package/dist/components/UiTextarea.vue.d.ts +16 -0
  20. package/dist/components/UiToaster.vue.d.ts +3 -0
  21. package/dist/composables/useToast.d.ts +27 -0
  22. package/{src/index.ts → dist/index.d.ts} +0 -2
  23. package/dist/minm-ui.css +2 -0
  24. package/dist/minm-ui.js +1041 -0
  25. package/dist/types.d.ts +9 -0
  26. package/package.json +29 -3
  27. package/src/components/UiAreaChart.vue +0 -117
  28. package/src/components/UiBadge.vue +0 -34
  29. package/src/components/UiBarChart.vue +0 -50
  30. package/src/components/UiButton.vue +0 -54
  31. package/src/components/UiCard.vue +0 -28
  32. package/src/components/UiDonutChart.vue +0 -73
  33. package/src/components/UiDrawer.vue +0 -65
  34. package/src/components/UiEmptyState.vue +0 -17
  35. package/src/components/UiIcon.vue +0 -76
  36. package/src/components/UiInput.vue +0 -50
  37. package/src/components/UiModal.vue +0 -63
  38. package/src/components/UiPagination.vue +0 -70
  39. package/src/components/UiSelect.vue +0 -53
  40. package/src/components/UiSkeleton.vue +0 -17
  41. package/src/components/UiSpinner.vue +0 -11
  42. package/src/components/UiStatTile.vue +0 -49
  43. package/src/components/UiTable.vue +0 -139
  44. package/src/components/UiTextarea.vue +0 -35
  45. package/src/components/UiToaster.vue +0 -47
  46. package/src/composables/useToast.ts +0 -43
  47. package/src/styles/tokens.css +0 -199
  48. package/src/types.ts +0 -9
@@ -0,0 +1,9 @@
1
+ /** Shared UI types. */
2
+ export interface Column {
3
+ key: string;
4
+ label: string;
5
+ sortable?: boolean;
6
+ align?: 'left' | 'right' | 'center';
7
+ width?: string;
8
+ class?: string;
9
+ }
package/package.json CHANGED
@@ -1,15 +1,41 @@
1
1
  {
2
2
  "name": "minm-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Minm UI shared design system — tokens + Vue 3 components.",
7
+ "style": "./dist/minm-ui.css",
8
+ "main": "./dist/minm-ui.js",
9
+ "module": "./dist/minm-ui.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "tailwind-preset.cjs",
14
+ "README.md"
15
+ ],
7
16
  "exports": {
8
- ".": "./src/index.ts",
9
- "./styles.css": "./src/styles/tokens.css",
17
+ ".": {
18
+ "import": "./dist/minm-ui.js",
19
+ "types": "./dist/index.d.ts"
20
+ },
21
+ "./styles.css": "./dist/minm-ui.css",
10
22
  "./tailwind-preset": "./tailwind-preset.cjs"
11
23
  },
24
+ "scripts": {
25
+ "build": "vite build",
26
+ "dev": "vite",
27
+ "prepack": "npm run build"
28
+ },
12
29
  "peerDependencies": {
13
30
  "vue": "^3.4.0"
31
+ },
32
+ "devDependencies": {
33
+ "@vitejs/plugin-vue": "^6.0.0",
34
+ "@vue/tsconfig": "^0.9.1",
35
+ "typescript": "^5.9.0",
36
+ "vite": "^8.1.4",
37
+ "vite-plugin-dts": "^5.0.3",
38
+ "vue": "^3.5.0",
39
+ "vue-tsc": "^3.3.7"
14
40
  }
15
41
  }
@@ -1,117 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref } from 'vue';
3
-
4
- interface Point { label: string; value: number }
5
-
6
- const props = withDefaults(defineProps<{
7
- data: Point[];
8
- height?: number;
9
- format?: (v: number) => string;
10
- }>(), { height: 220 });
11
-
12
- const W = 1000;
13
- const H = computed(() => props.height);
14
- const padTop = 12;
15
- const padBottom = 26;
16
- const padX = 8;
17
-
18
- const max = computed(() => Math.max(1, ...props.data.map((d) => d.value)));
19
-
20
- const coords = computed(() => {
21
- const n = props.data.length;
22
- const innerW = W - padX * 2;
23
- const innerH = H.value - padTop - padBottom;
24
- return props.data.map((d, i) => {
25
- const x = n <= 1 ? W / 2 : padX + (i / (n - 1)) * innerW;
26
- const y = padTop + innerH - (d.value / max.value) * innerH;
27
- return { x, y, ...d };
28
- });
29
- });
30
-
31
- const linePath = computed(() =>
32
- coords.value.map((c, i) => `${i === 0 ? 'M' : 'L'} ${c.x.toFixed(1)} ${c.y.toFixed(1)}`).join(' '),
33
- );
34
-
35
- const areaPath = computed(() => {
36
- if (!coords.value.length) return '';
37
- const baseline = H.value - padBottom;
38
- const first = coords.value[0];
39
- const last = coords.value[coords.value.length - 1];
40
- return `M ${first.x} ${baseline} ${coords.value.map((c) => `L ${c.x.toFixed(1)} ${c.y.toFixed(1)}`).join(' ')} L ${last.x} ${baseline} Z`;
41
- });
42
-
43
- const gridLines = computed(() => {
44
- const innerH = H.value - padTop - padBottom;
45
- return [0, 0.25, 0.5, 0.75, 1].map((t) => padTop + innerH * t);
46
- });
47
-
48
- const uid = Math.random().toString(36).slice(2, 8);
49
- const hover = ref<number | null>(null);
50
- const fmt = (v: number) => (props.format ? props.format(v) : String(v));
51
- // Show a subset of x labels to avoid crowding.
52
- const labelEvery = computed(() => Math.ceil(props.data.length / 6));
53
- </script>
54
-
55
- <template>
56
- <div class="relative w-full">
57
- <svg :viewBox="`0 0 ${W} ${H}`" :height="H" width="100%" preserveAspectRatio="none" class="overflow-visible">
58
- <defs>
59
- <linearGradient :id="`area-${uid}`" x1="0" y1="0" x2="0" y2="1">
60
- <stop offset="0%" stop-color="var(--accent)" stop-opacity="0.22" />
61
- <stop offset="100%" stop-color="var(--accent)" stop-opacity="0" />
62
- </linearGradient>
63
- </defs>
64
-
65
- <!-- faint grid -->
66
- <line
67
- v-for="(y, i) in gridLines"
68
- :key="i"
69
- :x1="padX" :x2="W - padX" :y1="y" :y2="y"
70
- stroke="var(--border)" stroke-width="1" vector-effect="non-scaling-stroke"
71
- :stroke-dasharray="i === gridLines.length - 1 ? '0' : '3 4'"
72
- />
73
-
74
- <path :d="areaPath" :fill="`url(#area-${uid})`" />
75
- <path :d="linePath" fill="none" stroke="var(--accent)" stroke-width="2.5"
76
- vector-effect="non-scaling-stroke" stroke-linejoin="round" stroke-linecap="round" />
77
-
78
- <!-- emphasized endpoint -->
79
- <circle
80
- v-if="coords.length"
81
- :cx="coords[coords.length - 1].x" :cy="coords[coords.length - 1].y" r="4"
82
- fill="var(--accent)" stroke="var(--surface)" stroke-width="2" vector-effect="non-scaling-stroke"
83
- />
84
-
85
- <!-- hover hit-areas + markers -->
86
- <g v-for="(c, i) in coords" :key="`h-${i}`">
87
- <circle
88
- v-if="hover === i" :cx="c.x" :cy="c.y" r="4"
89
- fill="var(--surface)" stroke="var(--accent)" stroke-width="2" vector-effect="non-scaling-stroke"
90
- />
91
- <rect
92
- :x="c.x - (W / coords.length) / 2" :y="0" :width="W / coords.length" :height="H"
93
- fill="transparent" @mouseenter="hover = i" @mouseleave="hover = null"
94
- />
95
- </g>
96
- </svg>
97
-
98
- <!-- x labels -->
99
- <div class="mt-1 flex justify-between px-1">
100
- <span
101
- v-for="(d, i) in data" :key="i"
102
- class="text-[11px] text-subtle"
103
- :style="{ visibility: i % labelEvery === 0 || i === data.length - 1 ? 'visible' : 'hidden' }"
104
- >{{ d.label }}</span>
105
- </div>
106
-
107
- <!-- tooltip -->
108
- <div
109
- v-if="hover !== null"
110
- class="pointer-events-none absolute -translate-x-1/2 -translate-y-full rounded-md border border-border bg-surface px-2.5 py-1.5 text-xs shadow-md"
111
- :style="{ left: `${(coords[hover].x / W) * 100}%`, top: `${(coords[hover].y / H) * 100}%` }"
112
- >
113
- <p class="font-semibold text-content tabular">{{ fmt(coords[hover].value) }}</p>
114
- <p class="text-subtle">{{ coords[hover].label }}</p>
115
- </div>
116
- </div>
117
- </template>
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue';
3
-
4
- const props = withDefaults(defineProps<{
5
- variant?: 'neutral' | 'success' | 'warning' | 'danger' | 'info' | 'accent';
6
- dot?: boolean;
7
- }>(), { variant: 'neutral' });
8
-
9
- const styles: Record<string, string> = {
10
- neutral: 'bg-surface-2 text-muted border-border',
11
- success: 'bg-success-bg text-success border-transparent',
12
- warning: 'bg-warning-bg text-warning border-transparent',
13
- danger: 'bg-danger-bg text-danger border-transparent',
14
- info: 'bg-info-bg text-info border-transparent',
15
- accent: 'bg-accent-soft text-accent-soft-fg border-transparent',
16
- };
17
-
18
- const dotColor = computed(() => ({
19
- neutral: 'bg-subtle', success: 'bg-success', warning: 'bg-warning',
20
- danger: 'bg-danger', info: 'bg-info', accent: 'bg-accent',
21
- }[props.variant]));
22
- </script>
23
-
24
- <template>
25
- <span
26
- :class="[
27
- 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium whitespace-nowrap',
28
- styles[variant],
29
- ]"
30
- >
31
- <span v-if="dot" :class="['h-1.5 w-1.5 rounded-full', dotColor]" />
32
- <slot />
33
- </span>
34
- </template>
@@ -1,50 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref } from 'vue';
3
-
4
- interface Point { label: string; value: number }
5
-
6
- const props = withDefaults(defineProps<{
7
- data: Point[];
8
- height?: number;
9
- format?: (v: number) => string;
10
- }>(), { height: 220 });
11
-
12
- const max = computed(() => Math.max(1, ...props.data.map((d) => d.value)));
13
- const hover = ref<number | null>(null);
14
- const fmt = (v: number) => (props.format ? props.format(v) : String(v));
15
- const labelEvery = computed(() => Math.ceil(props.data.length / 8));
16
- </script>
17
-
18
- <template>
19
- <div class="w-full">
20
- <div class="flex items-end gap-1.5" :style="{ height: `${height}px` }">
21
- <div
22
- v-for="(d, i) in data"
23
- :key="i"
24
- class="group relative flex flex-1 flex-col justify-end"
25
- @mouseenter="hover = i"
26
- @mouseleave="hover = null"
27
- >
28
- <div
29
- class="w-full rounded-t transition-all duration-200"
30
- :class="hover === i ? 'bg-accent' : 'bg-accent/55'"
31
- :style="{ height: `${Math.max(2, (d.value / max) * 100)}%` }"
32
- />
33
- <div
34
- v-if="hover === i"
35
- class="pointer-events-none absolute bottom-full left-1/2 mb-1.5 -translate-x-1/2 whitespace-nowrap rounded-md border border-border bg-surface px-2 py-1 text-xs shadow-md"
36
- >
37
- <span class="font-semibold text-content tabular">{{ fmt(d.value) }}</span>
38
- <span class="ml-1 text-subtle">{{ d.label }}</span>
39
- </div>
40
- </div>
41
- </div>
42
- <div class="mt-1.5 flex gap-1.5">
43
- <span
44
- v-for="(d, i) in data" :key="i"
45
- class="flex-1 truncate text-center text-[11px] text-subtle"
46
- :style="{ visibility: i % labelEvery === 0 || i === data.length - 1 ? 'visible' : 'hidden' }"
47
- >{{ d.label }}</span>
48
- </div>
49
- </div>
50
- </template>
@@ -1,54 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue';
3
- import UiIcon from './UiIcon.vue';
4
-
5
- const props = withDefaults(defineProps<{
6
- variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'subtle';
7
- size?: 'sm' | 'md' | 'lg';
8
- icon?: string;
9
- iconRight?: string;
10
- loading?: boolean;
11
- disabled?: boolean;
12
- block?: boolean;
13
- type?: 'button' | 'submit';
14
- }>(), {
15
- variant: 'primary',
16
- size: 'md',
17
- type: 'button',
18
- });
19
-
20
- const variants: Record<string, string> = {
21
- primary: 'bg-accent text-accent-fg hover:bg-accent-hover shadow-sm',
22
- secondary: 'bg-surface text-content border border-border hover:bg-surface-hover',
23
- ghost: 'text-muted hover:text-content hover:bg-surface-hover',
24
- subtle: 'bg-accent-soft text-accent-soft-fg hover:brightness-95',
25
- danger: 'bg-danger text-white hover:brightness-110 shadow-sm',
26
- };
27
-
28
- const sizes: Record<string, string> = {
29
- sm: 'h-8 px-3 text-[13px] gap-1.5 rounded-md',
30
- md: 'h-10 px-4 text-sm gap-2 rounded-md',
31
- lg: 'h-11 px-5 text-[15px] gap-2 rounded-lg',
32
- };
33
-
34
- const iconSize = computed(() => (props.size === 'sm' ? 15 : props.size === 'lg' ? 19 : 17));
35
- </script>
36
-
37
- <template>
38
- <button
39
- :type="type"
40
- :disabled="disabled || loading"
41
- :class="[
42
- 'inline-flex items-center justify-center font-medium transition-all duration-150 select-none',
43
- 'disabled:opacity-55 disabled:pointer-events-none whitespace-nowrap',
44
- variants[variant],
45
- sizes[size],
46
- block ? 'w-full' : '',
47
- ]"
48
- >
49
- <UiIcon v-if="loading" name="refresh" :size="iconSize" class="animate-spin" />
50
- <UiIcon v-else-if="icon" :name="icon" :size="iconSize" />
51
- <slot />
52
- <UiIcon v-if="iconRight && !loading" :name="iconRight" :size="iconSize" />
53
- </button>
54
- </template>
@@ -1,28 +0,0 @@
1
- <script setup lang="ts">
2
- withDefaults(defineProps<{
3
- title?: string;
4
- subtitle?: string;
5
- padded?: boolean;
6
- }>(), { padded: true });
7
- </script>
8
-
9
- <template>
10
- <section class="rounded-lg border border-border bg-surface shadow-sm">
11
- <header
12
- v-if="title || $slots.header || $slots.actions"
13
- class="flex items-center justify-between gap-4 border-b border-border px-5 py-4"
14
- >
15
- <div v-if="title || subtitle" class="min-w-0">
16
- <h3 class="truncate text-[15px] font-semibold text-content">{{ title }}</h3>
17
- <p v-if="subtitle" class="mt-0.5 truncate text-[13px] text-muted">{{ subtitle }}</p>
18
- </div>
19
- <slot name="header" />
20
- <div v-if="$slots.actions" class="flex shrink-0 items-center gap-2">
21
- <slot name="actions" />
22
- </div>
23
- </header>
24
- <div :class="padded ? 'p-5' : ''">
25
- <slot />
26
- </div>
27
- </section>
28
- </template>
@@ -1,73 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue';
3
-
4
- interface Segment { label: string; value: number; color?: string }
5
-
6
- const props = withDefaults(defineProps<{
7
- data: Segment[];
8
- size?: number;
9
- thickness?: number;
10
- centerLabel?: string;
11
- centerValue?: string | number;
12
- }>(), { size: 180, thickness: 20 });
13
-
14
- // Token-driven palette so slices read as one system.
15
- const palette = [
16
- 'var(--brand-500)', 'var(--info)', 'var(--warning)',
17
- 'var(--brand-300)', 'var(--danger)', 'var(--success)',
18
- ];
19
-
20
- const total = computed(() => props.data.reduce((s, d) => s + d.value, 0) || 1);
21
- const radius = computed(() => (props.size - props.thickness) / 2);
22
- const circ = computed(() => 2 * Math.PI * radius.value);
23
-
24
- const segments = computed(() => {
25
- let offset = 0;
26
- return props.data.map((d, i) => {
27
- const frac = d.value / total.value;
28
- const seg = {
29
- ...d,
30
- color: d.color || palette[i % palette.length],
31
- dash: frac * circ.value,
32
- gap: circ.value - frac * circ.value,
33
- offset: -offset * circ.value,
34
- pct: Math.round(frac * 100),
35
- };
36
- offset += frac;
37
- return seg;
38
- });
39
- });
40
- </script>
41
-
42
- <template>
43
- <div class="flex flex-wrap items-center gap-6">
44
- <div class="relative shrink-0" :style="{ width: `${size}px`, height: `${size}px` }">
45
- <svg :width="size" :height="size" :viewBox="`0 0 ${size} ${size}`" class="-rotate-90">
46
- <circle
47
- :cx="size / 2" :cy="size / 2" :r="radius"
48
- fill="none" stroke="var(--surface-2)" :stroke-width="thickness"
49
- />
50
- <circle
51
- v-for="(s, i) in segments" :key="i"
52
- :cx="size / 2" :cy="size / 2" :r="radius"
53
- fill="none" :stroke="s.color" :stroke-width="thickness"
54
- :stroke-dasharray="`${s.dash} ${s.gap}`" :stroke-dashoffset="s.offset"
55
- stroke-linecap="butt"
56
- />
57
- </svg>
58
- <div class="absolute inset-0 flex flex-col items-center justify-center">
59
- <span class="tabular text-2xl font-semibold text-content">{{ centerValue ?? total }}</span>
60
- <span v-if="centerLabel" class="text-[11px] uppercase tracking-wide text-subtle">{{ centerLabel }}</span>
61
- </div>
62
- </div>
63
-
64
- <ul class="flex min-w-0 flex-1 flex-col gap-2">
65
- <li v-for="(s, i) in segments" :key="i" class="flex items-center gap-2.5 text-sm">
66
- <span class="h-2.5 w-2.5 shrink-0 rounded-full" :style="{ background: s.color }" />
67
- <span class="truncate capitalize text-muted">{{ s.label }}</span>
68
- <span class="tabular ml-auto font-medium text-content">{{ s.value }}</span>
69
- <span class="tabular w-9 text-right text-xs text-subtle">{{ s.pct }}%</span>
70
- </li>
71
- </ul>
72
- </div>
73
- </template>
@@ -1,65 +0,0 @@
1
- <script setup lang="ts">
2
- import { watch, onUnmounted } from 'vue';
3
- import UiIcon from './UiIcon.vue';
4
-
5
- const props = defineProps<{ modelValue: boolean; title?: string; subtitle?: string; width?: string }>();
6
- const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void }>();
7
-
8
- function close() { emit('update:modelValue', false); }
9
-
10
- function onKey(e: KeyboardEvent) {
11
- if (e.key === 'Escape') close();
12
- }
13
-
14
- watch(() => props.modelValue, (open) => {
15
- document.body.style.overflow = open ? 'hidden' : '';
16
- if (open) document.addEventListener('keydown', onKey);
17
- else document.removeEventListener('keydown', onKey);
18
- });
19
-
20
- onUnmounted(() => {
21
- document.removeEventListener('keydown', onKey);
22
- document.body.style.overflow = '';
23
- });
24
- </script>
25
-
26
- <template>
27
- <Teleport to="body">
28
- <Transition name="overlay">
29
- <div
30
- v-if="modelValue"
31
- class="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm"
32
- @click.self="close"
33
- @keydown.esc="close"
34
- >
35
- <aside
36
- class="absolute right-0 top-0 flex h-full w-full flex-col border-l border-border bg-surface shadow-lg animate-slide-in-right"
37
- :style="{ maxWidth: width || '30rem' }"
38
- role="dialog"
39
- aria-modal="true"
40
- >
41
- <header class="flex items-start justify-between gap-4 border-b border-border px-6 py-4">
42
- <div class="min-w-0">
43
- <h3 class="truncate text-base font-semibold text-content">{{ title }}</h3>
44
- <p v-if="subtitle" class="mt-0.5 truncate text-[13px] text-muted">{{ subtitle }}</p>
45
- </div>
46
- <button class="rounded-md p-1 text-subtle transition-colors hover:bg-surface-hover hover:text-content" @click="close">
47
- <UiIcon name="x" :size="18" />
48
- </button>
49
- </header>
50
- <div class="flex-1 overflow-y-auto px-6 py-5">
51
- <slot />
52
- </div>
53
- <footer v-if="$slots.footer" class="flex items-center justify-end gap-2 border-t border-border px-6 py-4">
54
- <slot name="footer" />
55
- </footer>
56
- </aside>
57
- </div>
58
- </Transition>
59
- </Teleport>
60
- </template>
61
-
62
- <style scoped>
63
- .overlay-enter-active, .overlay-leave-active { transition: opacity 0.2s ease; }
64
- .overlay-enter-from, .overlay-leave-to { opacity: 0; }
65
- </style>
@@ -1,17 +0,0 @@
1
- <script setup lang="ts">
2
- import UiIcon from './UiIcon.vue';
3
- defineProps<{ icon?: string; title: string; description?: string }>();
4
- </script>
5
-
6
- <template>
7
- <div class="flex flex-col items-center justify-center px-6 py-14 text-center">
8
- <span class="mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-surface-2 text-subtle">
9
- <UiIcon :name="icon || 'search'" :size="26" />
10
- </span>
11
- <h4 class="text-[15px] font-semibold text-content">{{ title }}</h4>
12
- <p v-if="description" class="mt-1 max-w-sm text-sm text-muted">{{ description }}</p>
13
- <div v-if="$slots.default" class="mt-5">
14
- <slot />
15
- </div>
16
- </div>
17
- </template>
@@ -1,76 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * Lightweight inline icon set (stroke, 24×24, currentColor). No external deps,
4
- * works fully offline. Add new glyphs to the `paths` map by name.
5
- */
6
- import { computed } from 'vue';
7
-
8
- const props = withDefaults(defineProps<{ name: string; size?: number | string }>(), {
9
- size: 20,
10
- });
11
-
12
- // Each entry is the inner markup of a 0 0 24 24 stroke icon.
13
- const paths: Record<string, string> = {
14
- menu: '<path d="M3 6h18M3 12h18M3 18h18"/>',
15
- dashboard: '<rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/>',
16
- pin: '<path d="M12 21s-7-6.2-7-11a7 7 0 0 1 14 0c0 4.8-7 11-7 11z"/><circle cx="12" cy="10" r="2.5"/>',
17
- cabin: '<path d="M3 11.5 12 4l9 7.5"/><path d="M5 10v10h14V10"/><path d="M9 20v-5h6v5"/>',
18
- package: '<path d="M21 8 12 3 3 8v8l9 5 9-5V8z"/><path d="m3 8 9 5 9-5"/><path d="M12 13v8"/>',
19
- calendar: '<rect x="3" y="4.5" width="18" height="17" rx="2"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/>',
20
- users: '<path d="M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9.5" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13A4 4 0 0 1 16 11"/>',
21
- user: '<circle cx="12" cy="8" r="4"/><path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1"/>',
22
- shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
23
- card: '<rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/>',
24
- bell: '<path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/>',
25
- settings: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
26
- search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
27
- plus: '<path d="M12 5v14M5 12h14"/>',
28
- edit: '<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z"/>',
29
- trash: '<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V6"/><path d="M10 11v6M14 11v6"/>',
30
- x: '<path d="M18 6 6 18M6 6l12 12"/>',
31
- check: '<path d="M20 6 9 17l-5-5"/>',
32
- 'chevron-down': '<path d="m6 9 6 6 6-6"/>',
33
- 'chevron-up': '<path d="m18 15-6-6-6 6"/>',
34
- 'chevron-left': '<path d="m15 18-6-6 6-6"/>',
35
- 'chevron-right': '<path d="m9 18 6-6-6-6"/>',
36
- sun: '<circle cx="12" cy="12" r="4.5"/><path d="M12 1.5v2M12 20.5v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1.5 12h2M20.5 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/>',
37
- moon: '<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>',
38
- logout: '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/>',
39
- filter: '<path d="M3 4h18l-7 8v6l-4 2v-8z"/>',
40
- download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>',
41
- database: '<ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/>',
42
- broom: '<path d="M3 21h18"/><path d="M7 21c0-3 1-5 3-7l7-7"/><path d="M13 5l6 6"/><path d="M16 2l6 6"/>',
43
- alert: '<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>',
44
- 'arrow-up': '<path d="M12 19V5M5 12l7-7 7 7"/>',
45
- 'arrow-down': '<path d="M12 5v14M19 12l-7 7-7-7"/>',
46
- 'trend-up': '<path d="M23 6l-9.5 9.5-5-5L1 18"/><path d="M17 6h6v6"/>',
47
- 'trend-down': '<path d="M23 18l-9.5-9.5-5 5L1 6"/><path d="M17 18h6v-6"/>',
48
- eye: '<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/><circle cx="12" cy="12" r="3"/>',
49
- refresh: '<path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/>',
50
- globe: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18 15 15 0 0 1 0-18z"/>',
51
- tag: '<path d="M20.6 13.4 12 22l-9-9V4h9l8.6 8.6a2 2 0 0 1 0 2.8z"/><circle cx="7.5" cy="7.5" r="1.2"/>',
52
- check_circle: '<circle cx="12" cy="12" r="9"/><path d="m9 12 2 2 4-4"/>',
53
- clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
54
- logo: '<path d="M3 11.5 12 4l9 7.5"/><path d="M5 10v10h14V10"/><path d="M10 20v-4h4v4"/>',
55
- dollar: '<path d="M12 1v22M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>',
56
- activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
57
- };
58
-
59
- const inner = computed(() => paths[props.name] ?? paths.x);
60
- const dim = computed(() => (typeof props.size === 'number' ? `${props.size}px` : props.size));
61
- </script>
62
-
63
- <template>
64
- <svg
65
- :width="dim"
66
- :height="dim"
67
- viewBox="0 0 24 24"
68
- fill="none"
69
- stroke="currentColor"
70
- stroke-width="1.75"
71
- stroke-linecap="round"
72
- stroke-linejoin="round"
73
- aria-hidden="true"
74
- v-html="inner"
75
- />
76
- </template>
@@ -1,50 +0,0 @@
1
- <script setup lang="ts">
2
- import UiIcon from './UiIcon.vue';
3
-
4
- defineProps<{
5
- modelValue?: string | number;
6
- label?: string;
7
- type?: string;
8
- placeholder?: string;
9
- icon?: string;
10
- error?: string;
11
- hint?: string;
12
- disabled?: boolean;
13
- required?: boolean;
14
- }>();
15
-
16
- defineEmits<{ (e: 'update:modelValue', value: string): void }>();
17
- </script>
18
-
19
- <template>
20
- <label class="block">
21
- <span v-if="label" class="mb-1.5 block text-[13px] font-medium text-content">
22
- {{ label }}
23
- <span v-if="required" class="text-danger">*</span>
24
- </span>
25
- <div class="relative">
26
- <UiIcon
27
- v-if="icon"
28
- :name="icon"
29
- :size="17"
30
- class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-subtle"
31
- />
32
- <input
33
- :value="modelValue"
34
- :type="type || 'text'"
35
- :placeholder="placeholder"
36
- :disabled="disabled"
37
- :class="[
38
- 'h-10 w-full rounded-md border bg-surface text-sm text-content transition-colors',
39
- 'placeholder:text-subtle focus:outline-none focus:ring-2 focus:ring-[var(--ring)]',
40
- 'disabled:opacity-60',
41
- icon ? 'pl-9 pr-3' : 'px-3',
42
- error ? 'border-danger' : 'border-border focus:border-accent',
43
- ]"
44
- @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
45
- />
46
- </div>
47
- <p v-if="error" class="mt-1 text-xs text-danger">{{ error }}</p>
48
- <p v-else-if="hint" class="mt-1 text-xs text-muted">{{ hint }}</p>
49
- </label>
50
- </template>