@uniweb/kit 0.4.11 → 0.5.0

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/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@uniweb/kit",
3
- "version": "0.4.11",
3
+ "version": "0.5.0",
4
4
  "description": "Standard component library for Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/index.js",
8
8
  "./styled": "./src/styled/index.js",
9
9
  "./styles": "./src/styles/index.css",
10
- "./search": "./src/search/index.js"
10
+ "./search": "./src/search/index.js",
11
+ "./theme-tokens.css": "./src/theme-tokens.css"
11
12
  },
12
13
  "files": [
13
14
  "src",
@@ -39,7 +40,7 @@
39
40
  "fuse.js": "^7.0.0",
40
41
  "shiki": "^3.0.0",
41
42
  "tailwind-merge": "^2.6.0",
42
- "@uniweb/core": "0.3.10"
43
+ "@uniweb/core": "0.4.0"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "react": "^18.0.0 || ^19.0.0",
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DataPlaceholder
3
+ *
4
+ * Default loading UI for components waiting on runtime data.
5
+ * Renders an animated pulse placeholder.
6
+ *
7
+ * @example
8
+ * import { DataPlaceholder } from '@uniweb/kit'
9
+ *
10
+ * function ArticleList({ content, block }) {
11
+ * if (block.dataLoading) return <DataPlaceholder />
12
+ * return <ArticleGrid articles={content.data.articles} />
13
+ * }
14
+ */
15
+ export function DataPlaceholder({ lines = 3, className = '' }) {
16
+ return (
17
+ <div className={`animate-pulse space-y-4 py-8 ${className}`} role="status" aria-label="Loading">
18
+ {Array.from({ length: lines }, (_, i) => (
19
+ <div key={i} className="h-4 rounded" style={{
20
+ backgroundColor: 'var(--border, #e5e7eb)',
21
+ width: i === lines - 1 ? '60%' : '100%',
22
+ }} />
23
+ ))}
24
+ </div>
25
+ )
26
+ }
27
+
28
+ export default DataPlaceholder
@@ -12,7 +12,7 @@
12
12
 
13
13
  import React, { useState, useEffect, useMemo } from 'react'
14
14
  import { getUniweb } from '@uniweb/core'
15
- import { cn } from '../../utils/index.js'
15
+ import { cn, parseIconRef } from '../../utils/index.js'
16
16
 
17
17
  /**
18
18
  * Built-in demo icons (simple SVG paths)
@@ -138,11 +138,13 @@ export function Icon({
138
138
  errorComponent,
139
139
  ...props
140
140
  }) {
141
- // Normalize props (handle legacy icon object)
142
- const iconLibrary = library || (typeof icon === 'object' ? icon.library : null)
143
- const iconUrl = url || (typeof icon === 'string' ? icon : icon?.url)
141
+ // Normalize props handle icon as string ref ("lu-house", "lu:house"),
142
+ // object ({ library, name }), or URL
143
+ const parsedRef = typeof icon === 'string' ? parseIconRef(icon) : null
144
+ const iconLibrary = library || parsedRef?.library || (typeof icon === 'object' ? icon.library : null)
145
+ const iconName = name || parsedRef?.name || (typeof icon === 'object' ? icon.name : null)
146
+ const iconUrl = url || (!parsedRef && typeof icon === 'string' ? icon : icon?.url)
144
147
  const iconSvg = svg || (typeof icon === 'object' ? icon.svg : null)
145
- const iconName = name || (typeof icon === 'object' ? icon.name : null)
146
148
 
147
149
  // Check sync cache for SSR (pre-populated by prerender)
148
150
  const cachedSvg = useMemo(() => {
@@ -285,10 +285,11 @@ export function Link({
285
285
  )
286
286
  }
287
287
 
288
- // Fallback to regular anchor
288
+ // Fallback to regular anchor (SSG prerender — no React Router)
289
+ const basePath = website?.basePath || ''
289
290
  return (
290
291
  <a
291
- href={linkHref}
292
+ href={basePath + linkHref}
292
293
  title={linkTitle}
293
294
  className={className}
294
295
  {...props}
package/src/index.js CHANGED
@@ -46,6 +46,9 @@ export { MediaIcon } from './components/MediaIcon/index.js'
46
46
  // Files (plain version - for styled card, use @uniweb/kit/tailwind)
47
47
  export { Asset } from './components/Asset/index.js'
48
48
 
49
+ // Data loading
50
+ export { DataPlaceholder } from './components/DataPlaceholder.jsx'
51
+
49
52
  // Social
50
53
  export {
51
54
  SocialIcon,
@@ -95,6 +98,7 @@ export {
95
98
  isExternalUrl,
96
99
  isFileUrl,
97
100
  detectMediaType,
101
+ parseIconRef,
98
102
  // Locale utilities
99
103
  LOCALE_DISPLAY_NAMES,
100
104
  getLocaleLabel
@@ -0,0 +1,97 @@
1
+ /*
2
+ * Standard Theme Tokens for Tailwind v4
3
+ *
4
+ * Import this in your foundation's styles.css to register theme CSS variables
5
+ * with Tailwind, enabling utility classes like `text-heading`, `bg-surface`, etc.
6
+ *
7
+ * Usage:
8
+ * @import "tailwindcss";
9
+ * @import "@uniweb/kit/theme-tokens.css";
10
+ *
11
+ * The actual values come from the build system's theme CSS (injected at runtime).
12
+ * The build generates short names (--primary-600, --heading) and this file bridges
13
+ * them to the --color-* namespace that Tailwind v4 expects for utility classes.
14
+ *
15
+ * Foundations that want custom Tailwind names can skip this import and declare
16
+ * their own @theme inline block.
17
+ */
18
+
19
+ @theme inline {
20
+ /* ── Semantic tokens ── */
21
+ /* Values come from theme CSS context classes (light/medium/dark) */
22
+ --color-heading: var(--heading);
23
+ --color-surface: var(--bg);
24
+ --color-surface-subtle: var(--bg-subtle);
25
+ --color-surface-muted: var(--bg-muted);
26
+ --color-body: var(--text);
27
+ --color-muted: var(--text-muted);
28
+ --color-subtle: var(--text-subtle);
29
+ --color-link: var(--link);
30
+ --color-link-hover: var(--link-hover);
31
+ --color-edge: var(--border);
32
+ --color-edge-muted: var(--border-muted);
33
+ --color-ring: var(--ring);
34
+ --color-btn-primary: var(--btn-primary-bg);
35
+ --color-btn-primary-text: var(--btn-primary-text);
36
+ --color-btn-primary-hover: var(--btn-primary-hover);
37
+ --color-btn-secondary: var(--btn-secondary-bg);
38
+ --color-btn-secondary-text: var(--btn-secondary-text);
39
+ --color-btn-secondary-hover: var(--btn-secondary-hover);
40
+
41
+ /* ── Primary palette ── */
42
+ --color-primary-50: var(--primary-50, #eff6ff);
43
+ --color-primary-100: var(--primary-100, #dbeafe);
44
+ --color-primary-200: var(--primary-200, #bfdbfe);
45
+ --color-primary-300: var(--primary-300, #93c5fd);
46
+ --color-primary-400: var(--primary-400, #60a5fa);
47
+ --color-primary-500: var(--primary-500, #3b82f6);
48
+ --color-primary-600: var(--primary-600, #2563eb);
49
+ --color-primary-700: var(--primary-700, #1d4ed8);
50
+ --color-primary-800: var(--primary-800, #1e40af);
51
+ --color-primary-900: var(--primary-900, #1e3a8a);
52
+ --color-primary-950: var(--primary-950, #172554);
53
+
54
+ /* ── Secondary palette ── */
55
+ --color-secondary-50: var(--secondary-50, #f8fafc);
56
+ --color-secondary-100: var(--secondary-100, #f1f5f9);
57
+ --color-secondary-200: var(--secondary-200, #e2e8f0);
58
+ --color-secondary-300: var(--secondary-300, #cbd5e1);
59
+ --color-secondary-400: var(--secondary-400, #94a3b8);
60
+ --color-secondary-500: var(--secondary-500, #64748b);
61
+ --color-secondary-600: var(--secondary-600, #475569);
62
+ --color-secondary-700: var(--secondary-700, #334155);
63
+ --color-secondary-800: var(--secondary-800, #1e293b);
64
+ --color-secondary-900: var(--secondary-900, #0f172a);
65
+ --color-secondary-950: var(--secondary-950, #020617);
66
+
67
+ /* ── Accent palette ── */
68
+ --color-accent-50: var(--accent-50, #f5f3ff);
69
+ --color-accent-100: var(--accent-100, #ede9fe);
70
+ --color-accent-200: var(--accent-200, #ddd6fe);
71
+ --color-accent-300: var(--accent-300, #c4b5fd);
72
+ --color-accent-400: var(--accent-400, #a78bfa);
73
+ --color-accent-500: var(--accent-500, #8b5cf6);
74
+ --color-accent-600: var(--accent-600, #7c3aed);
75
+ --color-accent-700: var(--accent-700, #6d28d9);
76
+ --color-accent-800: var(--accent-800, #5b21b6);
77
+ --color-accent-900: var(--accent-900, #4c1d95);
78
+ --color-accent-950: var(--accent-950, #2e1065);
79
+
80
+ /* ── Neutral palette ── */
81
+ --color-neutral-50: var(--neutral-50, #fafafa);
82
+ --color-neutral-100: var(--neutral-100, #f4f4f5);
83
+ --color-neutral-200: var(--neutral-200, #e4e4e7);
84
+ --color-neutral-300: var(--neutral-300, #d4d4d8);
85
+ --color-neutral-400: var(--neutral-400, #a1a1aa);
86
+ --color-neutral-500: var(--neutral-500, #71717a);
87
+ --color-neutral-600: var(--neutral-600, #52525b);
88
+ --color-neutral-700: var(--neutral-700, #3f3f46);
89
+ --color-neutral-800: var(--neutral-800, #27272a);
90
+ --color-neutral-900: var(--neutral-900, #18181b);
91
+ --color-neutral-950: var(--neutral-950, #09090b);
92
+
93
+ /* ── Brand shorthands ── */
94
+ --color-primary: var(--primary-500, #3b82f6);
95
+ --color-secondary: var(--secondary-500, #64748b);
96
+ --color-accent: var(--accent-500, #8b5cf6);
97
+ }
@@ -78,6 +78,65 @@ export function getLocaleLabel(locale) {
78
78
  return locale.label || LOCALE_DISPLAY_NAMES[locale.code] || locale.code.toUpperCase()
79
79
  }
80
80
 
81
+ // ─────────────────────────────────────────────────────────────────
82
+ // Icon Utilities
83
+ // ─────────────────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Short icon family codes (2-3 chars) used for dash-format parsing.
87
+ * Matches the content-reader's ICON_FAMILIES_SHORT list.
88
+ */
89
+ const ICON_SHORT_CODES = [
90
+ 'lu', 'hi', 'hi2', 'pi', 'tb', 'fi', 'bs', 'md', 'ai',
91
+ 'ri', 'si', 'io5', 'bi', 'vsc', 'wi', 'gi', 'fa', 'fa6'
92
+ ]
93
+
94
+ const ICON_SHORT_CODE_SET = new Set(ICON_SHORT_CODES)
95
+
96
+ /**
97
+ * Parse an icon reference string into { library, name }.
98
+ *
99
+ * Accepts all standard icon formats:
100
+ * - Dash format: "lu-house", "hi2-arrow-right"
101
+ * - Colon format: "lu:house", "lucide:house"
102
+ *
103
+ * Returns null if the string doesn't match any known format.
104
+ *
105
+ * @param {string} ref - Icon reference string
106
+ * @returns {{ library: string, name: string } | null}
107
+ *
108
+ * @example
109
+ * parseIconRef('lu-house') // { library: 'lu', name: 'house' }
110
+ * parseIconRef('lu:house') // { library: 'lu', name: 'house' }
111
+ * parseIconRef('lucide:house') // { library: 'lucide', name: 'house' }
112
+ * parseIconRef('not-an-icon') // null
113
+ */
114
+ export function parseIconRef(ref) {
115
+ if (!ref || typeof ref !== 'string') return null
116
+
117
+ // Colon format: "lu:house", "lucide:house"
118
+ const colonIdx = ref.indexOf(':')
119
+ if (colonIdx > 0) {
120
+ return { library: ref.slice(0, colonIdx), name: ref.slice(colonIdx + 1) }
121
+ }
122
+
123
+ // Dash format: "lu-house" — only short codes (2-3 chars) to avoid
124
+ // ambiguity with regular hyphenated strings
125
+ const dashIdx = ref.indexOf('-')
126
+ if (dashIdx > 0) {
127
+ const prefix = ref.slice(0, dashIdx)
128
+ if (ICON_SHORT_CODE_SET.has(prefix)) {
129
+ return { library: prefix, name: ref.slice(dashIdx + 1) }
130
+ }
131
+ }
132
+
133
+ return null
134
+ }
135
+
136
+ // ─────────────────────────────────────────────────────────────────
137
+ // Class / String Utilities
138
+ // ─────────────────────────────────────────────────────────────────
139
+
81
140
  /**
82
141
  * Merge class names with Tailwind CSS conflict resolution
83
142
  * @param {...string} classes - Class names to merge