@viliha/vui-ui 1.6.3 → 1.7.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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ backward-compatible features, **major** for breaking changes.
7
7
 
8
8
  To upgrade, see [Upgrading](./AGENT.md#upgrading) in the agent guide.
9
9
 
10
+ ## 1.7.0 — 2026-07-25
11
+
12
+ ### Added
13
+
14
+ - **Configurable favicon** via the runtime brand system. `BrandProvider` gains a
15
+ `faviconUrl` field (alongside `logoUrl`): set `NEXT_PUBLIC_FAVICON_URL` for a
16
+ build-time default, return `faviconUrl` from your `NEXT_PUBLIC_BRAND_URL` JSON
17
+ (or call `useBrand().setBrand({ faviconUrl })`) to change the browser-tab icon
18
+ live per tenant, no rebuild. The static `app/icon.*` files remain the fallback.
19
+
10
20
  ## 1.6.3 — 2026-07-25
11
21
 
12
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viliha/vui-ui",
3
- "version": "1.6.3",
3
+ "version": "1.7.0",
4
4
  "description": "Vui UI — a clean, token-driven React admin/CRM component library built on Tailwind CSS v4, shadcn-style patterns, and Radix Icons. Ships as TypeScript source (Just-in-Time), compiled by the consuming app.",
5
5
  "license": "MIT",
6
6
  "author": "Suman Bonakurthi",
@@ -12,10 +12,11 @@ NEXT_PUBLIC_APP_DESCRIPTION="Your app description for search engines and social
12
12
  NEXT_PUBLIC_APP_URL="https://vui.viliha.com"
13
13
 
14
14
  # Runtime branding (multi-tenant / white-label). Point this at a JSON endpoint
15
- # returning any of { name, tagline, description, logoUrl, company, companyUrl };
16
- # BrandProvider fetches it on load and overrides the values above — including the
17
- # tab title — live, no rebuild. (Or seed <BrandProvider initial={…}> from a
18
- # loader, or call useBrand().setBrand(…) once your API responds.)
15
+ # returning any of { name, tagline, description, logoUrl, faviconUrl, company,
16
+ # companyUrl }; BrandProvider fetches it on load and overrides the values above —
17
+ # including the tab title and favicon — live, no rebuild. (Or seed
18
+ # <BrandProvider initial={…}> from a loader, or call useBrand().setBrand(…) once
19
+ # your API responds.)
19
20
  # NEXT_PUBLIC_BRAND_URL="https://api.example.com/branding"
20
21
 
21
22
  NEXT_PUBLIC_COMPANY_NAME="VILIHA PTE. LTD."
@@ -28,6 +29,10 @@ NEXT_PUBLIC_LICENSE="MIT Licensed"
28
29
  # mark. e.g. drop public/logo.svg and set:
29
30
  # NEXT_PUBLIC_LOGO_URL="/logo.svg"
30
31
 
32
+ # Optional — browser-tab icon (favicon). Overrides the static app/icon.* files;
33
+ # falls back to them when unset. Serve from /public or use an absolute URL.
34
+ # NEXT_PUBLIC_FAVICON_URL="/favicon.png"
35
+
31
36
  # Override the entire footer line at once (takes precedence over the vars above):
32
37
  # NEXT_PUBLIC_FOOTER_NOTICE="© 2026 Acme Inc. · All rights reserved"
33
38
 
@@ -26,6 +26,9 @@ export type Brand = {
26
26
  description: string;
27
27
  /** Overrides NEXT_PUBLIC_LOGO_URL at runtime; unset → the built-in mark. */
28
28
  logoUrl?: string;
29
+ /** Browser-tab icon. Overrides NEXT_PUBLIC_FAVICON_URL at runtime; unset →
30
+ * the static app/icon.* file convention. */
31
+ faviconUrl?: string;
29
32
  company: string;
30
33
  companyUrl: string;
31
34
  };
@@ -35,6 +38,7 @@ const DEFAULT_BRAND: Brand = {
35
38
  tagline: SITE.tagline,
36
39
  description: SITE.description,
37
40
  logoUrl: process.env.NEXT_PUBLIC_LOGO_URL || undefined,
41
+ faviconUrl: process.env.NEXT_PUBLIC_FAVICON_URL || undefined,
38
42
  company: SITE.company,
39
43
  companyUrl: SITE.companyUrl,
40
44
  };
@@ -106,6 +110,32 @@ export function BrandProvider({
106
110
  return () => cancelAnimationFrame(id);
107
111
  }, [pathname, brand.name, brand.tagline]);
108
112
 
113
+ // Swap the browser-tab icon when the brand provides one (env default or API).
114
+ // The static app/icon.* links are the pre-JS fallback; here we point every
115
+ // icon <link> at the brand favicon and restore them if it changes/unmounts.
116
+ React.useEffect(() => {
117
+ const href = brand.faviconUrl;
118
+ if (!href) return; // no override — keep the static file-convention icons
119
+ const links = Array.from(
120
+ document.querySelectorAll<HTMLLinkElement>("link[rel~='icon']"),
121
+ );
122
+ if (links.length === 0) {
123
+ const link = document.createElement("link");
124
+ link.rel = "icon";
125
+ document.head.appendChild(link);
126
+ links.push(link);
127
+ }
128
+ const prev = links.map((l) => l.getAttribute("href"));
129
+ links.forEach((l) => l.setAttribute("href", href));
130
+ return () => {
131
+ links.forEach((l, i) => {
132
+ const p = prev[i];
133
+ if (p == null) l.remove(); // was created/absent → drop it
134
+ else l.setAttribute("href", p);
135
+ });
136
+ };
137
+ }, [brand.faviconUrl]);
138
+
109
139
  return (
110
140
  <BrandContext.Provider value={{ brand, setBrand }}>
111
141
  {children}