@djangocfg/layouts 2.1.571 → 2.1.573

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.571",
3
+ "version": "2.1.573",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -90,11 +90,11 @@
90
90
  "check": "tsc --noEmit"
91
91
  },
92
92
  "peerDependencies": {
93
- "@djangocfg/analytics": "^2.1.571",
94
- "@djangocfg/api": "^2.1.571",
95
- "@djangocfg/devtools": "^2.1.571",
96
- "@djangocfg/i18n": "^2.1.571",
97
- "@djangocfg/ui-core": "^2.1.571",
93
+ "@djangocfg/analytics": "^2.1.573",
94
+ "@djangocfg/api": "^2.1.573",
95
+ "@djangocfg/devtools": "^2.1.573",
96
+ "@djangocfg/i18n": "^2.1.573",
97
+ "@djangocfg/ui-core": "^2.1.573",
98
98
  "@hookform/resolvers": "^5.2.2",
99
99
  "consola": "^3.4.2",
100
100
  "lucide-react": "^0.545.0",
@@ -124,12 +124,12 @@
124
124
  "uuid": "^11.1.1"
125
125
  },
126
126
  "devDependencies": {
127
- "@djangocfg/analytics": "^2.1.571",
128
- "@djangocfg/api": "^2.1.571",
129
- "@djangocfg/devtools": "^2.1.571",
130
- "@djangocfg/i18n": "^2.1.571",
131
- "@djangocfg/typescript-config": "^2.1.571",
132
- "@djangocfg/ui-core": "^2.1.571",
127
+ "@djangocfg/analytics": "^2.1.573",
128
+ "@djangocfg/api": "^2.1.573",
129
+ "@djangocfg/devtools": "^2.1.573",
130
+ "@djangocfg/i18n": "^2.1.573",
131
+ "@djangocfg/typescript-config": "^2.1.573",
132
+ "@djangocfg/ui-core": "^2.1.573",
133
133
  "@storybook/react-vite": "^10.5.0",
134
134
  "@types/node": "^25.9.5",
135
135
  "@types/react": "19.2.15",
@@ -6,22 +6,27 @@
6
6
  *
7
7
  * Usage:
8
8
  * ```tsx
9
- * // By name (string)
9
+ * // By name (string) — PascalCase or kebab-case
10
10
  * <LucideIcon icon="CloudLightning" className="w-4 h-4" />
11
11
  *
12
12
  * // By component
13
13
  * import { CloudLightning } from 'lucide-react';
14
14
  * <LucideIcon icon={CloudLightning} className="w-4 h-4" />
15
15
  * ```
16
+ *
17
+ * A name loads that one icon on demand. `import * as` from lucide-react put
18
+ * all ~1,600 icons (~180 KB gz) into every app that reached this barrel.
16
19
  */
17
20
 
18
21
  'use client';
19
22
 
20
- import * as LucideIcons from 'lucide-react';
23
+ import { CloudLightning } from 'lucide-react';
24
+ import { DynamicIcon, iconNames } from 'lucide-react/dynamic';
21
25
  import React from 'react';
22
26
 
23
27
  import { cn } from '@djangocfg/ui-core/lib';
24
28
 
29
+ import type { IconName } from 'lucide-react/dynamic';
25
30
  import type { LucideIcon as LucideIconType } from 'lucide-react';
26
31
  export interface LucideIconProps extends Omit<React.SVGProps<SVGSVGElement>, 'children'> {
27
32
  /**
@@ -41,52 +46,41 @@ export interface LucideIconProps extends Omit<React.SVGProps<SVGSVGElement>, 'ch
41
46
  className?: string;
42
47
  }
43
48
 
44
- /**
45
- * Get Lucide icon by name or return the icon component
46
- * @param icon - Icon name (string) or LucideIcon component
47
- * @param fallback - Fallback icon if name not found (default: CloudLightning)
48
- * @returns LucideIcon component
49
- */
50
- function getLucideIcon(
51
- icon: LucideIconType | string | undefined,
52
- fallback: LucideIconType = LucideIcons.CloudLightning
53
- ): LucideIconType {
54
- if (!icon) {
55
- return fallback;
56
- }
57
-
58
- if (typeof icon === 'string') {
59
- const IconComponent = LucideIcons[icon as keyof typeof LucideIcons] as LucideIconType | undefined;
60
- return IconComponent || fallback;
61
- }
49
+ // "ArrowUp01" and "arrow-up-0-1" both fold to "arrowup01".
50
+ let byFolded: Map<string, IconName> | undefined;
62
51
 
63
- return icon;
52
+ function resolveIconName(name: string): IconName | undefined {
53
+ byFolded ??= new Map(iconNames.map((n) => [n.replace(/-/g, ''), n as IconName]));
54
+ return byFolded.get(name.replace(/[-_\s]/g, '').toLowerCase());
64
55
  }
65
56
 
66
57
  /**
67
58
  * LucideIcon Component
68
59
  *
69
60
  * Renders a Lucide icon either by name (string) or by component reference.
70
- * Automatically resolves icon names from lucide-react package.
71
- *
72
- * Usage:
73
- * ```tsx
74
- * // By name (string)
75
- * <LucideIcon icon="CloudLightning" className="w-4 h-4" />
76
- *
77
- * // By component
78
- * import { CloudLightning } from 'lucide-react';
79
- * <LucideIcon icon={CloudLightning} className="w-4 h-4" />
80
- * ```
61
+ * A name renders an empty box of the same size until its icon loads.
81
62
  */
82
63
  export function LucideIcon({
83
64
  icon,
84
- fallback = LucideIcons.CloudLightning,
65
+ fallback = CloudLightning,
85
66
  className,
86
67
  ...props
87
68
  }: LucideIconProps) {
88
- const IconComponent = getLucideIcon(icon, fallback);
69
+ if (typeof icon === 'string') {
70
+ const name = resolveIconName(icon);
71
+ if (name) {
72
+ const Placeholder = () => <span aria-hidden className={cn('inline-block', className)} />;
73
+ return (
74
+ <DynamicIcon
75
+ {...(props as Omit<React.SVGProps<SVGSVGElement>, 'ref' | 'name'>)}
76
+ name={name}
77
+ className={cn(className)}
78
+ fallback={Placeholder}
79
+ />
80
+ );
81
+ }
82
+ }
89
83
 
84
+ const IconComponent = icon && typeof icon !== 'string' ? icon : fallback;
90
85
  return <IconComponent className={cn(className)} {...props} />;
91
86
  }
92
-
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { Check, FlaskConical, KeyRound, Loader2, RefreshCw } from 'lucide-react';
4
- import moment from 'moment';
5
4
  import React from 'react';
6
5
 
7
6
  import {
@@ -20,9 +19,23 @@ import { ApiKeyProvider, useApiKeyContext } from './context';
20
19
  // Helpers
21
20
  // ─────────────────────────────────────────────────────────────────────────────
22
21
 
22
+ // `Intl.DateTimeFormat`, not moment: this was the whole of moment's job here —
23
+ // one UTC timestamp rendered in the viewer's zone. moment ships its 137 locale
24
+ // files to every consumer that imports anything from this package's barrel,
25
+ // which cost the MLS property pages two 805 KB chunks (measured 2026-10-17).
26
+ //
27
+ // A bare `new Date(iso)` would read a timestamp without a zone designator as
28
+ // LOCAL time; the API sends UTC, so the `Z` is appended when it is missing.
23
29
  function formatDate(iso: string | null, fallback: string): string {
24
30
  if (!iso) return fallback;
25
- return moment.utc(iso).local().format('MMM D, YYYY');
31
+ const utc = /(?:Z|[+-]\d{2}:?\d{2})$/.test(iso) ? iso : `${iso}Z`;
32
+ const date = new Date(utc);
33
+ if (Number.isNaN(date.getTime())) return fallback;
34
+ return new Intl.DateTimeFormat('en-US', {
35
+ year: 'numeric',
36
+ month: 'short',
37
+ day: 'numeric',
38
+ }).format(date);
26
39
  }
27
40
 
28
41
  /** Returns true if the backend already masked the key (contains •). */