@bagelink/vue 1.15.82 → 1.15.86

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 (49) hide show
  1. package/dist/components/Alert.vue.d.ts +6 -1
  2. package/dist/components/Alert.vue.d.ts.map +1 -1
  3. package/dist/components/Avatar.vue.d.ts +3 -1
  4. package/dist/components/Avatar.vue.d.ts.map +1 -1
  5. package/dist/components/Badge.vue.d.ts +4 -0
  6. package/dist/components/Badge.vue.d.ts.map +1 -1
  7. package/dist/components/EmptyState.vue.d.ts.map +1 -1
  8. package/dist/components/Image.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  13. package/dist/components/index.d.ts +2 -0
  14. package/dist/components/index.d.ts.map +1 -1
  15. package/dist/components/kanban/KanbanBoard.vue.d.ts +42 -0
  16. package/dist/components/kanban/KanbanBoard.vue.d.ts.map +1 -0
  17. package/dist/components/kanban/kanbanTypes.d.ts +64 -0
  18. package/dist/components/kanban/kanbanTypes.d.ts.map +1 -0
  19. package/dist/composables/useGradientVariant.d.ts.map +1 -1
  20. package/dist/index.cjs +165 -138
  21. package/dist/index.mjs +35488 -41334
  22. package/dist/style.css +1 -2
  23. package/package.json +1 -1
  24. package/src/components/Alert.vue +40 -9
  25. package/src/components/Avatar.vue +8 -5
  26. package/src/components/Badge.vue +30 -0
  27. package/src/components/Dropdown.vue +1 -1
  28. package/src/components/EmptyState.vue +5 -1
  29. package/src/components/Image.vue +18 -4
  30. package/src/components/form/inputs/ArrayInput.vue +2 -2
  31. package/src/components/form/inputs/RangeInput.vue +0 -2
  32. package/src/components/form/inputs/RichText/components/TableGridSelector.vue +1 -1
  33. package/src/components/form/inputs/RichText/editor.css +14 -8
  34. package/src/components/form/inputs/RichText/index.vue +1 -1
  35. package/src/components/form/inputs/TextInput.vue +6 -2
  36. package/src/components/form/inputs/Upload/upload.css +7 -1
  37. package/src/components/index.ts +2 -0
  38. package/src/components/kanban/KanbanBoard.vue +231 -0
  39. package/src/components/kanban/kanbanTypes.ts +72 -0
  40. package/src/composables/useGradientVariant.ts +20 -3
  41. package/src/styles/appearance.css +108 -0
  42. package/src/styles/base-colors.css +830 -1
  43. package/src/styles/buttons.css +44 -2
  44. package/src/styles/color-variants.css +13 -0
  45. package/src/styles/colors.css +1115 -1
  46. package/src/styles/mobileColors.css +1156 -0
  47. package/src/styles/motion.css +107 -19
  48. package/src/styles/theme.css +84 -1
  49. package/vite.config.ts +2 -2
@@ -0,0 +1,231 @@
1
+ <script setup lang="ts" generic="T extends Record<string, any> = Record<string, any>">
2
+ import { computed, reactive, ref } from 'vue'
3
+ import Badge from '../Badge.vue'
4
+ import Btn from '../Btn.vue'
5
+ import EmptyState from '../EmptyState.vue'
6
+ import Loading from '../Loading.vue'
7
+ import type { KanbanBoardProps, KanbanColumn, KanbanMoveEvent } from './kanbanTypes'
8
+
9
+ defineOptions({ name: 'BglKanbanBoard' })
10
+
11
+ const props = withDefaults(defineProps<KanbanBoardProps<T>>(), {
12
+ emptyText: 'Nothing here',
13
+ })
14
+
15
+ const emit = defineEmits<{
16
+ move: [event: KanbanMoveEvent<T>]
17
+ 'card-click': [item: T, columnId: string]
18
+ }>()
19
+
20
+ const slots = defineSlots<{
21
+ card?: (p: { item: T; column: KanbanColumn<T> }) => any
22
+ 'column-header'?: (p: { column: KanbanColumn<T>; count: number }) => any
23
+ 'column-footer'?: (p: { column: KanbanColumn<T> }) => any
24
+ }>()
25
+
26
+ /* ── mode resolution ───────────────────────────────────────────── */
27
+ const serverMode = computed(() => Array.isArray(props.columns))
28
+
29
+ function read<V>(item: T, key: keyof T | ((i: T) => V) | undefined): V | undefined {
30
+ if (key == null) return undefined
31
+ return typeof key === 'function' ? key(item) : (item[key] as unknown as V)
32
+ }
33
+
34
+ const keyOf = (item: T): string | number => {
35
+ const k = props.itemKey
36
+ const v = k ? read<any>(item, k) : (item as any).id
37
+ return v ?? JSON.stringify(item)
38
+ }
39
+
40
+ /** Resolved columns with their items, for both modes. */
41
+ const resolvedColumns = computed<Array<KanbanColumn<T> & { items: T[] }>>(() => {
42
+ if (serverMode.value) {
43
+ return (props.columns ?? []).map(c => ({ ...c, items: c.items ?? [] }))
44
+ }
45
+ // group-by mode
46
+ const defs = props.columnsDef ?? []
47
+ const groupOf = (item: T) => String(read<string>(item, props.groupBy) ?? '')
48
+ const buckets = new Map<string, T[]>()
49
+ for (const c of defs) buckets.set(c.id, [])
50
+ for (const item of props.data ?? []) {
51
+ const g = groupOf(item)
52
+ if (!buckets.has(g)) {
53
+ // when no columnsDef provided, create columns on the fly
54
+ if (!defs.length) buckets.set(g, [])
55
+ else continue
56
+ }
57
+ buckets.get(g)!.push(item)
58
+ }
59
+ if (!defs.length) {
60
+ return [...buckets.entries()].map(([id, items]) => ({ id, title: id, items }))
61
+ }
62
+ return defs.map(c => ({ ...c, items: buckets.get(c.id) ?? [] }))
63
+ })
64
+
65
+ /* ── load more (one handler, per-column page tracking) ─────────── */
66
+ const pageByColumn = reactive<Record<string, number>>({})
67
+
68
+ function canLoadMore(col: KanbanColumn<T> & { items: T[] }) {
69
+ if (!props.loadMore) return false
70
+ if (typeof col.total === 'number') return col.items.length < col.total
71
+ return false
72
+ }
73
+ async function onLoadMore(col: KanbanColumn<T> & { items: T[] }) {
74
+ const next = (pageByColumn[col.id] ?? 1) + 1
75
+ pageByColumn[col.id] = next
76
+ await props.loadMore?.(col.id, next)
77
+ }
78
+
79
+ /* ── drag & drop (HTML5, cross-column, controlled) ─────────────── */
80
+ const dragging = ref<{ item: any; from: string } | null>(null)
81
+ const dragOverCol = ref<string | null>(null)
82
+
83
+ function onDragStart(item: T, from: string) {
84
+ if (!props.draggable) return
85
+ dragging.value = { item, from }
86
+ }
87
+ function onDragEnd() {
88
+ dragging.value = null
89
+ dragOverCol.value = null
90
+ }
91
+ function onDrop(toCol: KanbanColumn<T> & { items: T[] }, index?: number) {
92
+ if (!props.draggable || !dragging.value) return
93
+ const { item, from } = dragging.value
94
+ const newIndex = index ?? toCol.items.length
95
+ emit('move', { item: item as T, fromColumn: from, toColumn: toCol.id, newIndex })
96
+ onDragEnd()
97
+ }
98
+
99
+ /* ── default card field resolution ─────────────────────────────── */
100
+ function cardTitle(item: T) { return read<string>(item, props.cardSchema?.title) }
101
+ function cardSubtitle(item: T) { return read<string>(item, props.cardSchema?.subtitle) }
102
+ function cardId(item: T) { return read<string>(item, props.cardSchema?.id) }
103
+ function cardBadges(item: T) {
104
+ const out: Array<{ label: string; color?: any }> = []
105
+ for (const b of props.cardSchema?.badges ?? []) {
106
+ const v = read<any>(item, b)
107
+ if (v == null || v === '') continue
108
+ out.push(typeof v === 'object' ? v : { label: String(v) })
109
+ }
110
+ return out
111
+ }
112
+ </script>
113
+
114
+ <template>
115
+ <div class="bgl-kanban">
116
+ <div
117
+ v-for="col in resolvedColumns" :key="col.id"
118
+ class="bgl-kanban-col"
119
+ :class="{ 'is-dragover': dragOverCol === col.id }"
120
+ @dragover.prevent="draggable && (dragOverCol = col.id)"
121
+ @dragleave="dragOverCol === col.id && (dragOverCol = null)"
122
+ @drop="onDrop(col)"
123
+ >
124
+ <!-- header -->
125
+ <div class="bgl-kanban-head">
126
+ <slot name="column-header" :column="col" :count="col.items.length">
127
+ <Badge v-if="col.color" dot :color="col.color" :value="col.title" flat sm />
128
+ <span v-else class="bgl-kanban-title">
129
+ <Badge v-if="col.icon" :icon="col.icon" flat sm :value="col.title" />
130
+ <template v-else>{{ col.title }}</template>
131
+ </span>
132
+ <Badge soft color="gray" sm :value="String(typeof col.total === 'number' ? col.total : col.items.length)" class="ms-auto" />
133
+ </slot>
134
+ </div>
135
+
136
+ <!-- cards -->
137
+ <div class="bgl-kanban-list">
138
+ <div
139
+ v-for="(item, i) in col.items" :key="keyOf(item)"
140
+ class="bgl-kanban-card-wrap"
141
+ :draggable="draggable || undefined"
142
+ @dragstart="onDragStart(item, col.id)"
143
+ @dragend="onDragEnd"
144
+ @drop.stop="onDrop(col, i)"
145
+ @click="emit('card-click', item, col.id)"
146
+ >
147
+ <slot name="card" :item="item" :column="col">
148
+ <!-- default schema card -->
149
+ <div class="bgl-kanban-card">
150
+ <div v-if="cardTitle(item)" class="bgl-kanban-card-title">{{ cardTitle(item) }}</div>
151
+ <div v-if="cardSubtitle(item)" class="bgl-kanban-card-sub">{{ cardSubtitle(item) }}</div>
152
+ <div v-if="cardBadges(item).length" class="bgl-kanban-card-badges">
153
+ <Badge v-for="(b, bi) in cardBadges(item)" :key="bi" sm soft :color="b.color || 'gray'" :value="b.label" />
154
+ </div>
155
+ <div v-if="cardId(item)" class="bgl-kanban-card-foot">{{ cardId(item) }}</div>
156
+ </div>
157
+ </slot>
158
+ </div>
159
+
160
+ <EmptyState v-if="!col.items.length && !col.loading" compact icon="inbox" :title="emptyText" class="bgl-kanban-empty" />
161
+ <Loading v-if="col.loading" class="bgl-kanban-loading" />
162
+ </div>
163
+
164
+ <!-- footer / load more -->
165
+ <div v-if="canLoadMore(col) || slots['column-footer']" class="bgl-kanban-foot">
166
+ <slot name="column-footer" :column="col">
167
+ <Btn
168
+ v-if="canLoadMore(col)" flat thin sm full-width icon="expand_more"
169
+ :value="`Load more`" :disabled="col.loading" @click="onLoadMore(col)"
170
+ />
171
+ </slot>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </template>
176
+
177
+ <style scoped>
178
+ .bgl-kanban {
179
+ display: flex;
180
+ align-items: stretch;
181
+ gap: 1rem;
182
+ overflow-x: auto;
183
+ min-height: 0;
184
+ min-width: 0;
185
+ }
186
+ .bgl-kanban-col {
187
+ display: flex;
188
+ flex-direction: column;
189
+ gap: .6rem;
190
+ width: 280px;
191
+ min-width: 280px;
192
+ flex-shrink: 0;
193
+ border-radius: var(--bgl-radius, 12px);
194
+ transition: background .12s;
195
+ }
196
+ .bgl-kanban-col.is-dragover {
197
+ background: color-mix(in srgb, var(--bgl-primary) 8%, transparent);
198
+ outline: 1px dashed color-mix(in srgb, var(--bgl-primary) 40%, transparent);
199
+ outline-offset: 2px;
200
+ }
201
+ .bgl-kanban-head {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: .5rem;
205
+ padding: 0 .25rem;
206
+ }
207
+ .bgl-kanban-title { font-size: 13px; font-weight: 600; }
208
+ /* flex:1 → list fills the column so empty columns stay full-height drop targets */
209
+ .bgl-kanban-list { display: flex; flex-direction: column; gap: .6rem; flex: 1; min-height: 8px; }
210
+ .bgl-kanban-card-wrap { cursor: pointer; }
211
+ .bgl-kanban-card-wrap[draggable='true'] { cursor: grab; }
212
+ .bgl-kanban-card-wrap[draggable='true']:active { cursor: grabbing; }
213
+ .bgl-kanban-card {
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: .5rem;
217
+ padding: .75rem .85rem;
218
+ background: var(--bgl-box-bg);
219
+ border: 1px solid var(--bgl-border-color);
220
+ border-radius: var(--bgl-radius, 12px);
221
+ transition: border-color .12s, box-shadow .12s;
222
+ }
223
+ .bgl-kanban-card:hover { border-color: color-mix(in srgb, var(--bgl-primary) 50%, var(--bgl-border-color)); }
224
+ .bgl-kanban-card-title { font-size: 13px; font-weight: 600; line-height: 1.3; }
225
+ .bgl-kanban-card-sub { font-size: 12px; opacity: .7; }
226
+ .bgl-kanban-card-badges { display: flex; gap: .25rem; flex-wrap: wrap; }
227
+ .bgl-kanban-card-foot { font-size: 11px; font-family: ui-monospace, monospace; opacity: .5; }
228
+ .bgl-kanban-empty { padding: .75rem 0; opacity: .5; }
229
+ .bgl-kanban-loading { padding: 1rem; display: flex; justify-content: center; }
230
+ .bgl-kanban-foot { padding-top: .1rem; }
231
+ </style>
@@ -0,0 +1,72 @@
1
+ import type { IconType, ThemeType } from '../../types'
2
+
3
+ /**
4
+ * A single board column. In **controlled / server mode** the caller supplies the
5
+ * full array of columns (each owning its own `items` + `total`). In **group-by
6
+ * mode** columns are derived from flat `data` and only `columnsDef` (id/title/…)
7
+ * is needed for ordering & labels.
8
+ */
9
+ export interface KanbanColumn<T = any> {
10
+ id: string
11
+ title: string
12
+ items?: T[]
13
+ /** Total available for this column (server mode). When set, "Load more" shows while items.length < total. */
14
+ total?: number
15
+ /** This column is currently fetching (server mode) — disables the load-more button + shows a spinner. */
16
+ loading?: boolean
17
+ color?: ThemeType
18
+ icon?: IconType
19
+ }
20
+
21
+ /** Lean card field map — a board card has a fixed shape, unlike a table row. */
22
+ export interface KanbanCardSchema<T = any> {
23
+ /** Field used as the card title (bold, first line). */
24
+ title?: keyof T | ((item: T) => string)
25
+ /** Optional secondary line. */
26
+ subtitle?: keyof T | ((item: T) => string)
27
+ /** Small monospace identifier shown in the footer (e.g. an id). */
28
+ id?: keyof T | ((item: T) => string)
29
+ /** Fields rendered as badges in the card body. */
30
+ badges?: Array<keyof T | ((item: T) => { label: string; color?: ThemeType } | string | undefined)>
31
+ }
32
+
33
+ export interface KanbanMoveEvent<T = any> {
34
+ item: T
35
+ fromColumn: string
36
+ toColumn: string
37
+ /** Insertion index within the destination column. */
38
+ newIndex: number
39
+ }
40
+
41
+ export interface KanbanBoardProps<T = any> {
42
+ /* ── group-by mode ── */
43
+ /** Flat data; provide with `groupBy` to derive columns client-side. */
44
+ data?: T[]
45
+ /** Field on each item that maps to a column id. */
46
+ groupBy?: keyof T | ((item: T) => string)
47
+ /** Column order + labels (group-by mode). Items not matching any def are dropped. */
48
+ columnsDef?: KanbanColumn<T>[]
49
+
50
+ /* ── controlled / server mode ── */
51
+ /** Supplying `columns` switches the board to controlled (server) mode. */
52
+ columns?: KanbanColumn<T>[]
53
+
54
+ /** Stable key for each item (defaults to `item.id`). */
55
+ itemKey?: keyof T | ((item: T) => string | number)
56
+
57
+ /** Default card rendering. Ignored when the `#card` slot is used. */
58
+ cardSchema?: KanbanCardSchema<T>
59
+
60
+ /** Enable drag & drop between/within columns. The board stays controlled — handle `@move`. */
61
+ draggable?: boolean
62
+
63
+ /**
64
+ * Single load-more handler for every column. The board tracks the next page
65
+ * per column and calls this with the column id + page. The caller fetches and
66
+ * appends to that column's `items` (and updates `total`).
67
+ */
68
+ loadMore?: (columnId: string, page: number) => void | Promise<void>
69
+
70
+ /** Label for empty columns. */
71
+ emptyText?: string
72
+ }
@@ -40,6 +40,23 @@ function parseTones(value: GradientProp | undefined): string[] {
40
40
  return value.trim().split(/\s+/).filter(Boolean)
41
41
  }
42
42
 
43
+ /**
44
+ * Resolve a single stop token to a CSS color value. Accepts, in order:
45
+ * - a `var(...)` expression → used as-is
46
+ * - a raw custom property `--bgl-x` → wrapped in `var(--bgl-x)`
47
+ * - an explicit color (#hex, rgb(), hsl(), color-mix(), named like "white")
48
+ * → used as-is
49
+ * - a theme tone like "blue" / "primary-30" → wrapped in `var(--bgl-blue)`
50
+ */
51
+ function resolveStop(token: string): string {
52
+ const t = token.trim()
53
+ if (t.startsWith('var(') || t.startsWith('#') || /^(rgb|hsl|color-mix|linear-gradient)\(/.test(t)) {
54
+ return t
55
+ }
56
+ if (t.startsWith('--')) { return `var(${t})` }
57
+ return `var(--bgl-${t})`
58
+ }
59
+
43
60
  interface UseGradientVariantArgs {
44
61
  /** The `gradient` prop value. */
45
62
  gradient: MaybeRefOrGetter<GradientProp | undefined>
@@ -86,12 +103,12 @@ export function useGradientVariant({ gradient, gradientDir, color }: UseGradient
86
103
  // 0–1 stops → let the CSS auto-derive a darker second stop. Inject nothing.
87
104
  if (s.length < 2) { return style }
88
105
 
89
- style['--bgl-grad-from'] = `var(--bgl-${s[0]})`
90
- style['--bgl-grad-to'] = `var(--bgl-${s[s.length - 1]})`
106
+ style['--bgl-grad-from'] = resolveStop(s[0])
107
+ style['--bgl-grad-to'] = resolveStop(s[s.length - 1])
91
108
  // Middle stops become the `via` slot (comma-terminated, like gradients.css).
92
109
  const middle = s.slice(1, -1)
93
110
  if (middle.length) {
94
- style['--bgl-grad-via'] = `${middle.map(t => `var(--bgl-${t})`).join(', ')}, `
111
+ style['--bgl-grad-via'] = `${middle.map(resolveStop).join(', ')}, `
95
112
  }
96
113
  return style
97
114
  })
@@ -826,4 +826,112 @@
826
826
 
827
827
  .bg-gradient-to-bl {
828
828
  background-image: linear-gradient(to bottom left, var(--bgl-gradient-from, transparent), var(--bgl-gradient-to, transparent)) !important;
829
+ }
830
+
831
+ /* Numeric blur scale (filter) — .blur-10 … .blur-130 */
832
+ .blur-10 {
833
+ filter: blur(10px) !important;
834
+ }
835
+
836
+ .blur-20 {
837
+ filter: blur(20px) !important;
838
+ }
839
+
840
+ .blur-30 {
841
+ filter: blur(30px) !important;
842
+ }
843
+
844
+ .blur-40 {
845
+ filter: blur(40px) !important;
846
+ }
847
+
848
+ .blur-50 {
849
+ filter: blur(50px) !important;
850
+ }
851
+
852
+ .blur-60 {
853
+ filter: blur(60px) !important;
854
+ }
855
+
856
+ .blur-70 {
857
+ filter: blur(70px) !important;
858
+ }
859
+
860
+ .blur-80 {
861
+ filter: blur(80px) !important;
862
+ }
863
+
864
+ .blur-90 {
865
+ filter: blur(90px) !important;
866
+ }
867
+
868
+ .blur-100 {
869
+ filter: blur(100px) !important;
870
+ }
871
+
872
+ .blur-110 {
873
+ filter: blur(110px) !important;
874
+ }
875
+
876
+ .blur-120 {
877
+ filter: blur(120px) !important;
878
+ }
879
+
880
+ .blur-130 {
881
+ filter: blur(130px) !important;
882
+ }
883
+
884
+ /* Numeric blur scale — mobile (≤910px) */
885
+ @media screen and (max-width: 910px) {
886
+ .m_blur-10 {
887
+ filter: blur(10px) !important;
888
+ }
889
+
890
+ .m_blur-20 {
891
+ filter: blur(20px) !important;
892
+ }
893
+
894
+ .m_blur-30 {
895
+ filter: blur(30px) !important;
896
+ }
897
+
898
+ .m_blur-40 {
899
+ filter: blur(40px) !important;
900
+ }
901
+
902
+ .m_blur-50 {
903
+ filter: blur(50px) !important;
904
+ }
905
+
906
+ .m_blur-60 {
907
+ filter: blur(60px) !important;
908
+ }
909
+
910
+ .m_blur-70 {
911
+ filter: blur(70px) !important;
912
+ }
913
+
914
+ .m_blur-80 {
915
+ filter: blur(80px) !important;
916
+ }
917
+
918
+ .m_blur-90 {
919
+ filter: blur(90px) !important;
920
+ }
921
+
922
+ .m_blur-100 {
923
+ filter: blur(100px) !important;
924
+ }
925
+
926
+ .m_blur-110 {
927
+ filter: blur(110px) !important;
928
+ }
929
+
930
+ .m_blur-120 {
931
+ filter: blur(120px) !important;
932
+ }
933
+
934
+ .m_blur-130 {
935
+ filter: blur(130px) !important;
936
+ }
829
937
  }