@bagelink/vue 1.12.36 → 1.12.42

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 (42) hide show
  1. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  2. package/dist/components/Slider.vue.d.ts.map +1 -1
  3. package/dist/components/Spreadsheet/Index.vue.d.ts.map +1 -1
  4. package/dist/components/Spreadsheet/SpreadsheetCell.vue.d.ts.map +1 -1
  5. package/dist/components/Spreadsheet/SpreadsheetTable.vue.d.ts.map +1 -1
  6. package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
  7. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  9. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  10. package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
  11. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  12. package/dist/form-flow/MultiStepForm.vue.d.ts.map +1 -1
  13. package/dist/form-flow/form-flow.d.ts.map +1 -1
  14. package/dist/form-flow/schema-fields.d.ts +127 -0
  15. package/dist/form-flow/schema-fields.d.ts.map +1 -0
  16. package/dist/index.cjs +73 -73
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.mjs +7912 -7693
  19. package/dist/style.css +1 -1
  20. package/dist/utils/useSearch.d.ts.map +1 -1
  21. package/package.json +102 -101
  22. package/src/components/Slider.vue +2 -1
  23. package/src/components/Spreadsheet/Index.vue +3 -3
  24. package/src/components/Spreadsheet/SpreadsheetCell.vue +1 -1
  25. package/src/components/calendar/views/WeekView.vue +20 -5
  26. package/src/components/form/inputs/RadioGroup.vue +1 -0
  27. package/src/components/form/inputs/TextInput.vue +3 -3
  28. package/src/components/layout/AppSidebar.vue +8 -7
  29. package/src/dialog/DialogForm.vue +1 -1
  30. package/src/form-flow/FormFlow.vue +7 -5
  31. package/src/form-flow/MultiStepForm.vue +6 -3
  32. package/src/form-flow/form-flow.ts +4 -0
  33. package/src/form-flow/schema-fields.ts +470 -0
  34. package/src/index.ts +2 -0
  35. package/src/styles/base-colors.css +105 -0
  36. package/src/styles/colors.css +312 -0
  37. package/src/styles/mobileColors.css +312 -0
  38. package/src/styles/scrollbar.css +13 -1
  39. package/src/styles/text.css +138 -0
  40. package/src/styles/theme.css +15 -1
  41. package/src/utils/useSearch.ts +1 -1
  42. package/LICENSE +0 -21
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Schema Fields — JSON Schema ↔ FormField conversion utilities
3
+ *
4
+ * Provides bidirectional conversion between JSON Schema (with x- layout extensions)
5
+ * and the FormField representation used by form renderers and builders.
6
+ */
7
+
8
+ import { computed, type Ref } from 'vue'
9
+
10
+ // ─── Field Types ──────────────────────────────────────────────────────────────
11
+
12
+ export type FormFieldType =
13
+ | 'text' | 'textarea' | 'richtext' | 'markdown'
14
+ | 'number' | 'date' | 'email' | 'phone' | 'url'
15
+ | 'select' | 'radio' | 'checkbox' | 'toggle'
16
+ | 'file' | 'signature' | 'range' | 'color' | 'json'
17
+ | 'heading' | 'divider'
18
+
19
+ export interface SelectOption {
20
+ label: string
21
+ value: string
22
+ }
23
+
24
+ export interface FormFieldValidation {
25
+ minLength?: number
26
+ maxLength?: number
27
+ min?: number
28
+ max?: number
29
+ step?: number
30
+ pattern?: string
31
+ options?: SelectOption[]
32
+ }
33
+
34
+ export interface FormField {
35
+ id: string
36
+ key: string
37
+ type: FormFieldType
38
+ label: string
39
+ placeholder?: string
40
+ description?: string
41
+ required?: boolean
42
+ colSpan?: number
43
+ colStart?: number
44
+ rowSpan?: number
45
+ rowStart?: number
46
+ mColSpan?: number
47
+ mColStart?: number
48
+ mRowSpan?: number
49
+ mRowStart?: number
50
+ hideLabel?: boolean
51
+ richTextVariant?: 'full' | 'basic' | 'simple'
52
+ markdownShowFormatting?: boolean
53
+ textareaRows?: number
54
+ textareaAutoHeight?: boolean
55
+ numberLayout?: 'default' | 'vertical' | 'horizontal'
56
+ numberSpinner?: boolean
57
+ phoneOnlyCountries?: string
58
+ selectMultiselect?: boolean
59
+ fileMultiple?: boolean
60
+ fileHeight?: number
61
+ fileAccept?: string
62
+ fileTheme?: 'dropzone' | 'basic'
63
+ fileShowIcon?: boolean
64
+ fileIcon?: string
65
+ dateEnableTime?: boolean
66
+ dateMin?: string
67
+ dateMax?: string
68
+ rangeMulti?: boolean
69
+ radioThin?: boolean
70
+ radioHideRadio?: boolean
71
+ radioAlign?: 'start' | 'center' | 'end'
72
+ textIcon?: string
73
+ textIconStart?: string
74
+ validation?: FormFieldValidation
75
+ defaultValue?: any
76
+ content?: string
77
+ }
78
+
79
+ export interface JSONSchemaProperty {
80
+ type?: string
81
+ title?: string
82
+ description?: string
83
+ format?: string
84
+ enum?: any[]
85
+ minimum?: number
86
+ maximum?: number
87
+ minLength?: number
88
+ maxLength?: number
89
+ pattern?: string
90
+ multipleOf?: number
91
+ default?: any
92
+ 'x-col-span'?: number
93
+ 'x-col-start'?: number
94
+ 'x-row-span'?: number
95
+ 'x-row-start'?: number
96
+ 'x-m-col-span'?: number
97
+ 'x-m-col-start'?: number
98
+ 'x-m-row-span'?: number
99
+ 'x-m-row-start'?: number
100
+ 'x-field-type'?: FormFieldType
101
+ 'x-placeholder'?: string
102
+ 'x-options'?: SelectOption[]
103
+ 'x-rich-text-variant'?: 'full' | 'basic' | 'simple'
104
+ 'x-textarea-rows'?: number
105
+ 'x-textarea-autoheight'?: boolean
106
+ 'x-number-layout'?: 'default' | 'vertical' | 'horizontal'
107
+ 'x-number-spinner'?: boolean
108
+ 'x-phone-only-countries'?: string
109
+ 'x-select-multiselect'?: boolean
110
+ 'x-file-multiple'?: boolean
111
+ 'x-file-height'?: number
112
+ 'x-file-accept'?: string
113
+ 'x-file-theme'?: 'dropzone' | 'basic'
114
+ 'x-file-show-icon'?: boolean
115
+ 'x-file-icon'?: string
116
+ 'x-date-enable-time'?: boolean
117
+ 'x-date-min'?: string
118
+ 'x-date-max'?: string
119
+ 'x-range-multi'?: boolean
120
+ 'x-radio-thin'?: boolean
121
+ 'x-radio-hide-radio'?: boolean
122
+ 'x-radio-align'?: 'start' | 'center' | 'end'
123
+ 'x-text-icon'?: string
124
+ 'x-text-icon-start'?: string
125
+ 'x-hide-label'?: boolean
126
+ 'x-markdown-show-formatting'?: boolean
127
+ }
128
+
129
+ export interface JSONSchemaObject {
130
+ $schema?: string
131
+ type: 'object'
132
+ properties?: Record<string, JSONSchemaProperty>
133
+ required?: string[]
134
+ 'x-dynamic-height'?: boolean
135
+ }
136
+
137
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
138
+
139
+ export function defaultRowSpan(type: FormFieldType): number {
140
+ if (type === 'richtext') return 3
141
+ if (type === 'textarea' || type === 'json' || type === 'radio' || type === 'file' || type === 'signature') return 2
142
+ return 1
143
+ }
144
+
145
+ export function inferFieldType(prop: JSONSchemaProperty): FormFieldType {
146
+ if (prop.format) {
147
+ switch (prop.format) {
148
+ case 'email': return 'email'
149
+ case 'tel': return 'phone'
150
+ case 'uri':
151
+ case 'url': return 'url'
152
+ case 'date':
153
+ case 'date-time': return 'date'
154
+ }
155
+ }
156
+ if (prop.enum) return 'select'
157
+ switch (prop.type) {
158
+ case 'number':
159
+ case 'integer': return 'number'
160
+ case 'boolean': return 'checkbox'
161
+ }
162
+ return 'text'
163
+ }
164
+
165
+ // ─── Schema → Fields ─────────────────────────────────────────────────────────
166
+
167
+ export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[] {
168
+ if (!schema?.properties) return []
169
+
170
+ const requiredSet = new Set(schema.required ?? [])
171
+ const fields: FormField[] = []
172
+
173
+ for (const [key, prop] of Object.entries(schema.properties)) {
174
+ const type = prop['x-field-type'] ?? inferFieldType(prop)
175
+
176
+ const validation: FormFieldValidation = {}
177
+ if (prop.minLength !== undefined) validation.minLength = prop.minLength
178
+ if (prop.maxLength !== undefined) validation.maxLength = prop.maxLength
179
+ if (prop.minimum !== undefined) validation.min = prop.minimum
180
+ if (prop.maximum !== undefined) validation.max = prop.maximum
181
+ if (prop.multipleOf !== undefined) validation.step = prop.multipleOf
182
+ if (prop.pattern !== undefined) validation.pattern = prop.pattern
183
+ if (prop['x-options']) {
184
+ validation.options = prop['x-options']
185
+ } else if (prop.enum) {
186
+ validation.options = prop.enum.map((v: any) => ({ label: String(v), value: String(v) }))
187
+ }
188
+
189
+ const field: FormField = {
190
+ id: crypto.randomUUID(),
191
+ key,
192
+ type,
193
+ label: prop.title ?? key,
194
+ placeholder: prop['x-placeholder'],
195
+ description: prop.description,
196
+ required: requiredSet.has(key) || undefined,
197
+ colSpan: prop['x-col-span'] ?? 12,
198
+ colStart: prop['x-col-start'],
199
+ rowSpan: prop['x-row-span'] ?? defaultRowSpan(type),
200
+ rowStart: prop['x-row-start'],
201
+ mColSpan: prop['x-m-col-span'] ?? 12,
202
+ mColStart: prop['x-m-col-start'],
203
+ mRowSpan: prop['x-m-row-span'],
204
+ mRowStart: prop['x-m-row-start'],
205
+ hideLabel: prop['x-hide-label'] ?? false,
206
+ richTextVariant: prop['x-rich-text-variant'],
207
+ markdownShowFormatting: prop['x-markdown-show-formatting'],
208
+ textareaRows: prop['x-textarea-rows'],
209
+ textareaAutoHeight: prop['x-textarea-autoheight'],
210
+ numberLayout: prop['x-number-layout'],
211
+ numberSpinner: prop['x-number-spinner'] ?? true,
212
+ phoneOnlyCountries: prop['x-phone-only-countries'],
213
+ selectMultiselect: prop['x-select-multiselect'],
214
+ fileMultiple: prop['x-file-multiple'],
215
+ fileHeight: prop['x-file-height'],
216
+ fileAccept: prop['x-file-accept'],
217
+ fileTheme: prop['x-file-theme'],
218
+ fileShowIcon: prop['x-file-show-icon'] === false ? false : undefined,
219
+ fileIcon: prop['x-file-icon'],
220
+ dateEnableTime: prop['x-date-enable-time'],
221
+ dateMin: prop['x-date-min'],
222
+ dateMax: prop['x-date-max'],
223
+ rangeMulti: prop['x-range-multi'],
224
+ radioThin: prop['x-radio-thin'],
225
+ radioHideRadio: prop['x-radio-hide-radio'],
226
+ radioAlign: prop['x-radio-align'],
227
+ textIcon: prop['x-text-icon'],
228
+ textIconStart: prop['x-text-icon-start'],
229
+ validation: Object.keys(validation).length > 0 ? validation : undefined,
230
+ defaultValue: prop.default,
231
+ content: prop['x-field-type'] === 'heading' ? prop.title : undefined,
232
+ }
233
+
234
+ fields.push(field)
235
+ }
236
+
237
+ return fields
238
+ }
239
+
240
+ // ─── Fields → Schema ─────────────────────────────────────────────────────────
241
+
242
+ export function fieldsToSchema(fields: FormField[], options?: { dynamicHeight?: boolean }): JSONSchemaObject {
243
+ const properties: Record<string, JSONSchemaProperty> = {}
244
+ const required: string[] = []
245
+
246
+ for (const field of fields) {
247
+ const prop: JSONSchemaProperty = {}
248
+
249
+ // Layout-only fields
250
+ if (field.type === 'heading' || field.type === 'divider') {
251
+ prop.type = 'string'
252
+ prop.title = field.content || field.label
253
+ prop['x-field-type'] = field.type
254
+ } else {
255
+ // Map field type to JSON Schema type
256
+ switch (field.type) {
257
+ case 'text':
258
+ case 'textarea':
259
+ case 'richtext':
260
+ case 'markdown':
261
+ case 'email':
262
+ case 'phone':
263
+ case 'url':
264
+ case 'date':
265
+ case 'file':
266
+ case 'signature':
267
+ case 'color':
268
+ prop.type = 'string'
269
+ break
270
+ case 'number':
271
+ case 'range':
272
+ prop.type = 'number'
273
+ break
274
+ case 'checkbox':
275
+ case 'toggle':
276
+ prop.type = 'boolean'
277
+ break
278
+ case 'json':
279
+ prop.type = 'object'
280
+ break
281
+ case 'select':
282
+ case 'radio':
283
+ prop.type = 'string'
284
+ if (field.validation?.options) {
285
+ prop.enum = field.validation.options.map(o => o.value)
286
+ }
287
+ break
288
+ }
289
+
290
+ // Format hints
291
+ if (field.type === 'email') prop.format = 'email'
292
+ if (field.type === 'phone') prop.format = 'tel'
293
+ if (field.type === 'url') prop.format = 'uri'
294
+ if (field.type === 'date') prop.format = field.dateEnableTime ? 'date-time' : 'date'
295
+ if (field.type === 'color') prop.format = 'color'
296
+
297
+ // Title & description
298
+ prop.title = field.label
299
+ if (field.description) prop.description = field.description
300
+ if (field.defaultValue !== undefined) prop.default = field.defaultValue
301
+
302
+ // x-field-type (skip for types that can be inferred)
303
+ if (field.type !== 'text') {
304
+ prop['x-field-type'] = field.type
305
+ }
306
+
307
+ // Validation constraints
308
+ if (field.validation?.minLength !== undefined) prop.minLength = field.validation.minLength
309
+ if (field.validation?.maxLength !== undefined) prop.maxLength = field.validation.maxLength
310
+ if (field.validation?.min !== undefined) prop.minimum = field.validation.min
311
+ if (field.validation?.max !== undefined) prop.maximum = field.validation.max
312
+ if (field.validation?.step !== undefined) prop.multipleOf = field.validation.step
313
+ if (field.validation?.pattern !== undefined) prop.pattern = field.validation.pattern
314
+
315
+ // Required
316
+ if (field.required) required.push(field.key)
317
+ }
318
+
319
+ // Placeholder
320
+ if (field.placeholder) prop['x-placeholder'] = field.placeholder
321
+
322
+ // Options
323
+ if (field.validation?.options) prop['x-options'] = field.validation.options
324
+
325
+ // Layout extensions — only write when non-default
326
+ if (field.colSpan !== undefined && field.colSpan !== 12) prop['x-col-span'] = field.colSpan
327
+ if (field.colStart !== undefined) prop['x-col-start'] = field.colStart
328
+ if (field.rowSpan !== undefined && field.rowSpan !== defaultRowSpan(field.type)) prop['x-row-span'] = field.rowSpan
329
+ if (field.rowStart !== undefined) prop['x-row-start'] = field.rowStart
330
+ if (field.mColSpan !== undefined && field.mColSpan !== 12) prop['x-m-col-span'] = field.mColSpan
331
+ if (field.mColStart !== undefined) prop['x-m-col-start'] = field.mColStart
332
+ if (field.mRowSpan !== undefined) prop['x-m-row-span'] = field.mRowSpan
333
+ if (field.mRowStart !== undefined) prop['x-m-row-start'] = field.mRowStart
334
+
335
+ // Boolean/enum x- extensions — only write when non-default
336
+ if (field.hideLabel === true) prop['x-hide-label'] = true
337
+ if (field.richTextVariant) prop['x-rich-text-variant'] = field.richTextVariant
338
+ if (field.markdownShowFormatting === true) prop['x-markdown-show-formatting'] = true
339
+ if (field.textareaRows !== undefined) prop['x-textarea-rows'] = field.textareaRows
340
+ if (field.textareaAutoHeight === true) prop['x-textarea-autoheight'] = true
341
+ if (field.numberLayout && field.numberLayout !== 'default') prop['x-number-layout'] = field.numberLayout
342
+ if (field.numberSpinner === false) prop['x-number-spinner'] = false
343
+ if (field.phoneOnlyCountries) prop['x-phone-only-countries'] = field.phoneOnlyCountries
344
+ if (field.selectMultiselect === true) prop['x-select-multiselect'] = true
345
+ if (field.fileMultiple === true) prop['x-file-multiple'] = true
346
+ if (field.fileHeight !== undefined) prop['x-file-height'] = field.fileHeight
347
+ if (field.fileAccept) prop['x-file-accept'] = field.fileAccept
348
+ if (field.fileTheme) prop['x-file-theme'] = field.fileTheme
349
+ if (field.fileShowIcon === false) prop['x-file-show-icon'] = false
350
+ if (field.fileIcon) prop['x-file-icon'] = field.fileIcon
351
+ if (field.dateEnableTime === true) prop['x-date-enable-time'] = true
352
+ if (field.dateMin) prop['x-date-min'] = field.dateMin
353
+ if (field.dateMax) prop['x-date-max'] = field.dateMax
354
+ if (field.rangeMulti === true) prop['x-range-multi'] = true
355
+ if (field.radioThin === true) prop['x-radio-thin'] = true
356
+ if (field.radioHideRadio === true) prop['x-radio-hide-radio'] = true
357
+ if (field.radioAlign) prop['x-radio-align'] = field.radioAlign
358
+ if (field.textIcon) prop['x-text-icon'] = field.textIcon
359
+ if (field.textIconStart) prop['x-text-icon-start'] = field.textIconStart
360
+
361
+ properties[field.key] = prop
362
+ }
363
+
364
+ const schema: JSONSchemaObject = {
365
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
366
+ type: 'object',
367
+ properties,
368
+ }
369
+
370
+ if (required.length > 0) schema.required = required
371
+ if (options?.dynamicHeight) schema['x-dynamic-height'] = true
372
+
373
+ return schema
374
+ }
375
+
376
+ // ─── Form Data Utilities ─────────────────────────────────────────────────────
377
+
378
+ export function initFormData(fields: FormField[], existing?: Record<string, any>): Record<string, any> {
379
+ const data: Record<string, any> = {}
380
+
381
+ for (const field of fields) {
382
+ if (field.type === 'heading' || field.type === 'divider') continue
383
+
384
+ if (existing && field.key in existing) {
385
+ data[field.key] = existing[field.key]
386
+ continue
387
+ }
388
+
389
+ if (field.defaultValue !== undefined) {
390
+ data[field.key] = field.defaultValue
391
+ continue
392
+ }
393
+
394
+ switch (field.type) {
395
+ case 'checkbox':
396
+ case 'toggle':
397
+ data[field.key] = false
398
+ break
399
+ case 'number':
400
+ case 'range':
401
+ data[field.key] = field.validation?.min ?? 0
402
+ break
403
+ default:
404
+ data[field.key] = ''
405
+ }
406
+ }
407
+
408
+ return data
409
+ }
410
+
411
+ export function validateFormData(fields: FormField[], data: Record<string, any>): Record<string, string> {
412
+ const errors: Record<string, string> = {}
413
+
414
+ for (const field of fields) {
415
+ if (field.type === 'heading' || field.type === 'divider') continue
416
+
417
+ const value = data[field.key]
418
+
419
+ // Required check
420
+ if (field.required && (value === undefined || value === null || value === '')) {
421
+ errors[field.key] = `${field.label} is required`
422
+ continue
423
+ }
424
+
425
+ // Skip further validation if empty and not required
426
+ if (value === undefined || value === null || value === '') continue
427
+
428
+ const v = field.validation
429
+
430
+ // String length checks
431
+ if (typeof value === 'string') {
432
+ if (v?.minLength !== undefined && value.length < v.minLength) {
433
+ errors[field.key] = `${field.label} must be at least ${v.minLength} characters`
434
+ continue
435
+ }
436
+ if (v?.maxLength !== undefined && value.length > v.maxLength) {
437
+ errors[field.key] = `${field.label} must be at most ${v.maxLength} characters`
438
+ continue
439
+ }
440
+ if (v?.pattern) {
441
+ const regex = new RegExp(v.pattern)
442
+ if (!regex.test(value)) {
443
+ errors[field.key] = `${field.label} format is invalid`
444
+ continue
445
+ }
446
+ }
447
+ }
448
+
449
+ // Number range checks
450
+ if (typeof value === 'number') {
451
+ if (v?.min !== undefined && value < v.min) {
452
+ errors[field.key] = `${field.label} must be at least ${v.min}`
453
+ continue
454
+ }
455
+ if (v?.max !== undefined && value > v.max) {
456
+ errors[field.key] = `${field.label} must be at most ${v.max}`
457
+ continue
458
+ }
459
+ }
460
+ }
461
+
462
+ return errors
463
+ }
464
+
465
+ // ─── Vue Composable ──────────────────────────────────────────────────────────
466
+
467
+ export function useSchemaToFields(schema: Ref<JSONSchemaObject | undefined>) {
468
+ const fields = computed(() => schemaToFields(schema.value))
469
+ return { fields }
470
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export * from './directives'
10
10
  export * from './form-flow/form-flow'
11
11
  export { default as FormFlow } from './form-flow/FormFlow.vue'
12
12
  export { default as MultiStepForm } from './form-flow/MultiStepForm.vue'
13
+ export type { FormFieldType, FormFieldValidation, FormField, JSONSchemaProperty, JSONSchemaObject } from './form-flow/schema-fields'
14
+ export { defaultRowSpan, inferFieldType, schemaToFields, fieldsToSchema, initFormData, validateFormData, useSchemaToFields } from './form-flow/schema-fields'
13
15
  // i18n exports
14
16
  export {
15
17
  bagelinkLocales,
@@ -214,6 +214,12 @@
214
214
  }
215
215
 
216
216
  /* 10% Variants (lightest) */
217
+ .pair-primary-10 {
218
+ background-color: var(--bgl-primary-10);
219
+ color: var(--bgl-black);
220
+ border-color: var(--bgl-primary-10);
221
+ }
222
+
217
223
  .pair-blue-10 {
218
224
  background-color: var(--bgl-blue-10);
219
225
  color: var(--bgl-black);
@@ -335,6 +341,12 @@
335
341
  border-color: var(--bgl-pink-10);
336
342
  }
337
343
 
344
+ .pair-primary-10-alt {
345
+ background-color: var(--bgl-primary-10);
346
+ color: var(--bgl-primary);
347
+ border-color: var(--bgl-primary-10);
348
+ }
349
+
338
350
  /* 20% Variants */
339
351
  .pair-blue-20 {
340
352
  background-color: var(--bgl-blue-20);
@@ -402,6 +414,12 @@
402
414
  border-color: var(--bgl-pink-20);
403
415
  }
404
416
 
417
+ .pair-primary-20 {
418
+ background-color: var(--bgl-primary-20);
419
+ color: var(--bgl-black);
420
+ border-color: var(--bgl-primary-20);
421
+ }
422
+
405
423
  /* 20% Alt Variants (colored text) */
406
424
  .pair-blue-20-alt {
407
425
  background-color: var(--bgl-blue-20);
@@ -457,6 +475,12 @@
457
475
  border-color: var(--bgl-pink-20);
458
476
  }
459
477
 
478
+ .pair-primary-20-alt {
479
+ background-color: var(--bgl-primary-20);
480
+ color: var(--bgl-primary);
481
+ border-color: var(--bgl-primary-20);
482
+ }
483
+
460
484
  /* 30% Variants */
461
485
  .pair-blue-30 {
462
486
  background-color: var(--bgl-blue-30);
@@ -524,6 +548,12 @@
524
548
  border-color: var(--bgl-pink-30);
525
549
  }
526
550
 
551
+ .pair-primary-30 {
552
+ background-color: var(--bgl-primary-30);
553
+ color: var(--bgl-black);
554
+ border-color: var(--bgl-primary-30);
555
+ }
556
+
527
557
  /* 30% Alt Variants (colored text) */
528
558
  .pair-blue-30-alt {
529
559
  background-color: var(--bgl-blue-30);
@@ -579,6 +609,12 @@
579
609
  border-color: var(--bgl-pink-30);
580
610
  }
581
611
 
612
+ .pair-primary-30-alt {
613
+ background-color: var(--bgl-primary-30);
614
+ color: var(--bgl-primary);
615
+ border-color: var(--bgl-primary-30);
616
+ }
617
+
582
618
  /* 40% Variants */
583
619
  .pair-blue-40 {
584
620
  background-color: var(--bgl-blue-40);
@@ -646,6 +682,12 @@
646
682
  border-color: var(--bgl-pink-40);
647
683
  }
648
684
 
685
+ .pair-primary-40 {
686
+ background-color: var(--bgl-primary-40);
687
+ color: var(--bgl-black);
688
+ border-color: var(--bgl-primary-40);
689
+ }
690
+
649
691
  /* 40% Alt Variants (colored text) */
650
692
  .pair-blue-40-alt {
651
693
  background-color: var(--bgl-blue-40);
@@ -701,6 +743,12 @@
701
743
  border-color: var(--bgl-pink-40);
702
744
  }
703
745
 
746
+ .pair-primary-40-alt {
747
+ background-color: var(--bgl-primary-40);
748
+ color: var(--bgl-primary);
749
+ border-color: var(--bgl-primary-40);
750
+ }
751
+
704
752
  /* 50% Variants */
705
753
  .pair-blue-50 {
706
754
  background-color: var(--bgl-blue-50);
@@ -768,6 +816,12 @@
768
816
  border-color: var(--bgl-pink-50);
769
817
  }
770
818
 
819
+ .pair-primary-50 {
820
+ background-color: var(--bgl-primary-50);
821
+ color: var(--bgl-black);
822
+ border-color: var(--bgl-primary-50);
823
+ }
824
+
771
825
  /* 50% Alt Variants (colored text) - none needed as all are already black text in 50% */
772
826
 
773
827
  /* 60% Variants */
@@ -837,6 +891,12 @@
837
891
  border-color: var(--bgl-pink-60);
838
892
  }
839
893
 
894
+ .pair-primary-60 {
895
+ background-color: var(--bgl-primary-60);
896
+ color: var(--bgl-white);
897
+ border-color: var(--bgl-primary-60);
898
+ }
899
+
840
900
  /* 70%-130% Variants (darker colors with white text) */
841
901
  .pair-blue-70,
842
902
  .pair-blue-80,
@@ -948,6 +1008,16 @@
948
1008
  color: var(--bgl-white);
949
1009
  }
950
1010
 
1011
+ .pair-primary-70,
1012
+ .pair-primary-80,
1013
+ .pair-primary-90,
1014
+ .pair-primary-100,
1015
+ .pair-primary-110,
1016
+ .pair-primary-120,
1017
+ .pair-primary-130 {
1018
+ color: var(--bgl-white);
1019
+ }
1020
+
951
1021
  /* Individual background definitions for 70%-130% */
952
1022
  .pair-blue-70 {
953
1023
  background-color: var(--bgl-blue-70);
@@ -1004,6 +1074,11 @@
1004
1074
  border-color: var(--bgl-pink-70);
1005
1075
  }
1006
1076
 
1077
+ .pair-primary-70 {
1078
+ background-color: var(--bgl-primary-70);
1079
+ border-color: var(--bgl-primary-70);
1080
+ }
1081
+
1007
1082
  .pair-blue-80 {
1008
1083
  background-color: var(--bgl-blue-80);
1009
1084
  border-color: var(--bgl-blue-80);
@@ -1059,6 +1134,11 @@
1059
1134
  border-color: var(--bgl-pink-80);
1060
1135
  }
1061
1136
 
1137
+ .pair-primary-80 {
1138
+ background-color: var(--bgl-primary-80);
1139
+ border-color: var(--bgl-primary-80);
1140
+ }
1141
+
1062
1142
  .pair-blue-90 {
1063
1143
  background-color: var(--bgl-blue-90);
1064
1144
  border-color: var(--bgl-blue-90);
@@ -1114,6 +1194,11 @@
1114
1194
  border-color: var(--bgl-pink-90);
1115
1195
  }
1116
1196
 
1197
+ .pair-primary-90 {
1198
+ background-color: var(--bgl-primary-90);
1199
+ border-color: var(--bgl-primary-90);
1200
+ }
1201
+
1117
1202
  .pair-blue-100 {
1118
1203
  background-color: var(--bgl-blue-100);
1119
1204
  border-color: var(--bgl-blue-100);
@@ -1169,6 +1254,11 @@
1169
1254
  border-color: var(--bgl-pink-100);
1170
1255
  }
1171
1256
 
1257
+ .pair-primary-100 {
1258
+ background-color: var(--bgl-primary-100);
1259
+ border-color: var(--bgl-primary-100);
1260
+ }
1261
+
1172
1262
  .pair-blue-110 {
1173
1263
  background-color: var(--bgl-blue-110);
1174
1264
  border-color: var(--bgl-blue-110);
@@ -1224,6 +1314,11 @@
1224
1314
  border-color: var(--bgl-pink-110);
1225
1315
  }
1226
1316
 
1317
+ .pair-primary-110 {
1318
+ background-color: var(--bgl-primary-110);
1319
+ border-color: var(--bgl-primary-110);
1320
+ }
1321
+
1227
1322
  .pair-blue-120 {
1228
1323
  background-color: var(--bgl-blue-120);
1229
1324
  border-color: var(--bgl-blue-120);
@@ -1279,6 +1374,11 @@
1279
1374
  border-color: var(--bgl-pink-120);
1280
1375
  }
1281
1376
 
1377
+ .pair-primary-120 {
1378
+ background-color: var(--bgl-primary-120);
1379
+ border-color: var(--bgl-primary-120);
1380
+ }
1381
+
1282
1382
  .pair-blue-130 {
1283
1383
  background-color: var(--bgl-blue-130);
1284
1384
  border-color: var(--bgl-blue-130);
@@ -1334,6 +1434,11 @@
1334
1434
  border-color: var(--bgl-pink-130);
1335
1435
  }
1336
1436
 
1437
+ .pair-primary-130 {
1438
+ background-color: var(--bgl-primary-130);
1439
+ border-color: var(--bgl-primary-130);
1440
+ }
1441
+
1337
1442
  /* Alt Variants - With colored text */
1338
1443
 
1339
1444
  /* Semantic Light Variants */