@brightlocal/icons 2.1.1 → 2.2.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightlocal/icons",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "BrightLocal Design System Icons - Lucide defaults and custom icons",
5
5
  "type": "module",
6
6
  "private": false,
package/src/dynamic.ts CHANGED
@@ -28,3 +28,7 @@ export type { FlagIconName } from "./flag/dynamic-imports.js";
28
28
 
29
29
  export { socialMediaIconNames } from "./social-media/dynamic-imports.js";
30
30
  export type { SocialMediaIconName } from "./social-media/dynamic-imports.js";
31
+
32
+ // Higher-level flag wrapper with unknown-country fallback and loading skeleton
33
+ export { DynamicFlagIcon } from "./flag/flag-icon.js";
34
+ export type { DynamicFlagIconProps } from "./flag/flag-icon.js";
@@ -0,0 +1,70 @@
1
+ import * as React from "react";
2
+
3
+ import { DynamicIcon } from "../icons/dynamic-icon.js";
4
+ import { flagIconNames, type FlagIconName } from "./dynamic-imports.js";
5
+ import { Globe } from "../icons/lucide-exports.js";
6
+
7
+ export interface DynamicFlagIconProps
8
+ extends React.ComponentProps<"span"> {
9
+ /** ISO 3166-1 alpha-3 country code (e.g. "USA", "GBR") */
10
+ country: string;
11
+ /**
12
+ * Icon size in pixels (width and height)
13
+ * @default 20
14
+ */
15
+ size?: number;
16
+ }
17
+
18
+ const flagIconNameSet = new Set<string>(flagIconNames);
19
+
20
+ const DynamicFlagIcon = React.forwardRef<HTMLSpanElement, DynamicFlagIconProps>(
21
+ ({ country, size = 20, className, style, ...props }, ref) => {
22
+ const isKnown = flagIconNameSet.has(country);
23
+
24
+ return (
25
+ <span
26
+ ref={ref}
27
+ aria-hidden="true"
28
+ className={className}
29
+ data-slot="flag-icon"
30
+ style={{
31
+ display: "inline-flex",
32
+ alignItems: "center",
33
+ justifyContent: "center",
34
+ flexShrink: 0,
35
+ width: size,
36
+ height: size,
37
+ ...style,
38
+ }}
39
+ {...props}
40
+ >
41
+ {isKnown ? (
42
+ <DynamicIcon
43
+ name={country as FlagIconName}
44
+ size={size}
45
+ />
46
+ ) : (
47
+ <span
48
+ data-slot="flag-icon-fallback"
49
+ style={{
50
+ display: "inline-flex",
51
+ alignItems: "center",
52
+ justifyContent: "center",
53
+ width: size,
54
+ height: size,
55
+ borderRadius: "9999px",
56
+ backgroundColor: "var(--color-muted, #f3f4f6)",
57
+ color: "var(--color-muted-foreground, #6b7280)",
58
+ }}
59
+ >
60
+ <Globe size={Math.round(size * 0.65)} />
61
+ </span>
62
+ )}
63
+ </span>
64
+ );
65
+ }
66
+ );
67
+
68
+ DynamicFlagIcon.displayName = "DynamicFlagIcon";
69
+
70
+ export { DynamicFlagIcon };
@@ -37,7 +37,7 @@ export interface DynamicIconProps extends React.ComponentProps<"svg"> {
37
37
  absoluteStrokeWidth?: boolean;
38
38
  /**
39
39
  * Fallback content displayed while the icon is loading.
40
- * @default null
40
+ * Defaults to an invisible placeholder that matches the icon's dimensions to prevent layout shift.
41
41
  */
42
42
  fallback?: React.ReactNode;
43
43
  }
@@ -85,6 +85,10 @@ for (const [name, loader] of Object.entries(iconImports)) {
85
85
  * <DynamicIcon name="camera" size={24} fallback={<Skeleton />} />
86
86
  *
87
87
  * @example
88
+ * // Opt out of default placeholder
89
+ * <DynamicIcon name="camera" size={24} fallback={null} />
90
+ *
91
+ * @example
88
92
  * // Database-driven
89
93
  * <DynamicIcon name={country.iso} size={16} />
90
94
  */
@@ -95,7 +99,7 @@ export const DynamicIcon = React.forwardRef<SVGSVGElement, DynamicIconProps>(
95
99
  size = 16,
96
100
  strokeWidth = 1.33,
97
101
  absoluteStrokeWidth = true,
98
- fallback = null,
102
+ fallback,
99
103
  ...props
100
104
  },
101
105
  ref
@@ -114,8 +118,23 @@ export const DynamicIcon = React.forwardRef<SVGSVGElement, DynamicIconProps>(
114
118
  ? { ref, size, strokeWidth, absoluteStrokeWidth, ...props }
115
119
  : { ref, size, ...props };
116
120
 
121
+ const placeholder =
122
+ fallback !== undefined ? (
123
+ fallback
124
+ ) : (
125
+ <span
126
+ aria-hidden="true"
127
+ style={{
128
+ display: "inline-block",
129
+ width: size,
130
+ height: size,
131
+ flexShrink: 0,
132
+ }}
133
+ />
134
+ );
135
+
117
136
  return (
118
- <React.Suspense fallback={fallback}>
137
+ <React.Suspense fallback={placeholder}>
119
138
  <Icon {...iconProps} />
120
139
  </React.Suspense>
121
140
  );
package/src/index.ts CHANGED
@@ -11,6 +11,10 @@ export * from "./icons/lucide-exports.js";
11
11
  export { createLucideIcon } from "lucide-react";
12
12
  export type { IconNode, LucideProps } from "lucide-react";
13
13
 
14
+ // Higher-level flag wrapper with unknown-country fallback and loading skeleton
15
+ export { DynamicFlagIcon } from "./flag/flag-icon.js";
16
+ export type { DynamicFlagIconProps } from "./flag/flag-icon.js";
17
+
14
18
  // Flag icons - Full list available via barrel exports
15
19
  // All icons are React SVG components with proper fill colors
16
20
  export * from "./flag/index.js";