@mvriu5/payload-icon-picker 0.1.1 → 0.1.4
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/README.md +19 -10
- package/dist/IconField.js +1 -10
- package/dist/adapters/lucide.d.ts +4 -0
- package/dist/adapters/lucide.js +2 -0
- package/dist/adapters/tabler.d.ts +4 -0
- package/dist/adapters/tabler.js +2 -0
- package/dist/adapters/utils.d.ts +7 -0
- package/dist/adapters/utils.js +77 -0
- package/package.json +30 -19
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ import { lexicalEditor } from "@payloadcms/richtext-lexical"
|
|
|
24
24
|
import * as Icons from "lucide-react"
|
|
25
25
|
import { buildConfig } from "payload"
|
|
26
26
|
import { iconField, payloadIconPlugin } from "@mvriu5/payload-icon-picker"
|
|
27
|
+
import { lucideIconAdapter } from "@mvriu5/payload-icon-picker/adapters/lucide"
|
|
27
28
|
|
|
28
29
|
export default buildConfig({
|
|
29
30
|
collections: [
|
|
@@ -44,8 +45,7 @@ export default buildConfig({
|
|
|
44
45
|
],
|
|
45
46
|
plugins: [
|
|
46
47
|
payloadIconPlugin({
|
|
47
|
-
icons: Icons,
|
|
48
|
-
resolveIcon: ({ name }) => name,
|
|
48
|
+
icons: lucideIconAdapter(Icons),
|
|
49
49
|
}),
|
|
50
50
|
],
|
|
51
51
|
db: postgresAdapter({
|
|
@@ -84,33 +84,42 @@ Use `createIconResolver()` to turn a stored string back into the registered icon
|
|
|
84
84
|
```tsx
|
|
85
85
|
import * as Icons from "lucide-react"
|
|
86
86
|
import { createIconResolver } from "@mvriu5/payload-icon-picker"
|
|
87
|
+
import { lucideIconAdapter } from "@mvriu5/payload-icon-picker/adapters/lucide"
|
|
87
88
|
|
|
88
89
|
const resolveStoredIcon = createIconResolver({
|
|
89
|
-
icons: Icons,
|
|
90
|
-
resolveIcon: ({ name }) => name,
|
|
90
|
+
icons: lucideIconAdapter(Icons),
|
|
91
91
|
})
|
|
92
92
|
|
|
93
93
|
export function PostIcon({ icon }: { icon?: string }) {
|
|
94
94
|
const resolvedIcon = resolveStoredIcon(icon)
|
|
95
|
-
const Icon = resolvedIcon?.Icon
|
|
96
95
|
|
|
97
|
-
if (!
|
|
96
|
+
if (!resolvedIcon?.svg) {
|
|
98
97
|
return null
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
return <
|
|
100
|
+
return <span aria-hidden dangerouslySetInnerHTML={{ __html: resolvedIcon.svg }} />
|
|
102
101
|
}
|
|
103
102
|
```
|
|
104
103
|
|
|
105
104
|
## Icon Inputs
|
|
106
105
|
|
|
107
|
-
|
|
106
|
+
Use an adapter for supported icon libraries. Adapters convert React icon exports to serializable SVG metadata for the Payload admin UI.
|
|
108
107
|
|
|
109
108
|
```ts
|
|
110
109
|
import * as Icons from "lucide-react"
|
|
110
|
+
import { lucideIconAdapter } from "@mvriu5/payload-icon-picker/adapters/lucide"
|
|
111
111
|
|
|
112
112
|
payloadIconPlugin({
|
|
113
|
-
icons: Icons,
|
|
113
|
+
icons: lucideIconAdapter(Icons),
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import * as TablerIcons from "@tabler/icons-react"
|
|
119
|
+
import { tablerIconAdapter } from "@mvriu5/payload-icon-picker/adapters/tabler"
|
|
120
|
+
|
|
121
|
+
payloadIconPlugin({
|
|
122
|
+
icons: tablerIconAdapter(TablerIcons),
|
|
114
123
|
})
|
|
115
124
|
```
|
|
116
125
|
|
|
@@ -149,7 +158,7 @@ iconField({
|
|
|
149
158
|
|
|
150
159
|
## Development
|
|
151
160
|
|
|
152
|
-
The dev Payload config in `dev/payload.config.ts` registers the plugin with
|
|
161
|
+
The dev Payload config in `dev/payload.config.ts` registers the plugin with `lucide-react`, which is installed as a development dependency.
|
|
153
162
|
|
|
154
163
|
```bash
|
|
155
164
|
pnpm dev
|
package/dist/IconField.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { FieldLabel, useField } from "@payloadcms/ui";
|
|
4
|
-
import { icons as lucideIcons } from "lucide-react";
|
|
5
4
|
import React, { useMemo, useState } from "react";
|
|
6
5
|
const defaultResolveIcon = (icon)=>icon.value ?? icon.name;
|
|
7
6
|
const ICON_BATCH_SIZE = 120;
|
|
@@ -131,7 +130,7 @@ export const IconField = ({ field, icons: iconsFromProps, noResultsLabel = "No i
|
|
|
131
130
|
textOverflow: "ellipsis",
|
|
132
131
|
whiteSpace: "nowrap"
|
|
133
132
|
},
|
|
134
|
-
children: selectedIcon ? selectedIcon.label ?? selectedIcon.name : placeholder
|
|
133
|
+
children: selectedIcon ? selectedIcon.label ?? selectedIcon.name : value || placeholder
|
|
135
134
|
}),
|
|
136
135
|
value ? /*#__PURE__*/ _jsx("span", {
|
|
137
136
|
onClick: (event)=>{
|
|
@@ -326,7 +325,6 @@ export const IconField = ({ field, icons: iconsFromProps, noResultsLabel = "No i
|
|
|
326
325
|
};
|
|
327
326
|
const IconPreview = ({ icon })=>{
|
|
328
327
|
const PreviewIcon = icon.Icon ?? icon.component;
|
|
329
|
-
const LucideIcon = lucideIcons[icon.name];
|
|
330
328
|
if (PreviewIcon) {
|
|
331
329
|
return /*#__PURE__*/ _jsx(PreviewIcon, {
|
|
332
330
|
"aria-hidden": true,
|
|
@@ -334,13 +332,6 @@ const IconPreview = ({ icon })=>{
|
|
|
334
332
|
size: 24
|
|
335
333
|
});
|
|
336
334
|
}
|
|
337
|
-
if (LucideIcon) {
|
|
338
|
-
return /*#__PURE__*/ _jsx(LucideIcon, {
|
|
339
|
-
"aria-hidden": true,
|
|
340
|
-
focusable: false,
|
|
341
|
-
size: 24
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
335
|
if (icon.svg) {
|
|
345
336
|
return /*#__PURE__*/ _jsx("span", {
|
|
346
337
|
"aria-hidden": true,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IconFieldIcon } from "../IconField.js";
|
|
2
|
+
import type { IconAdapterOptions, IconLibrary } from "./utils.js";
|
|
3
|
+
export type LucideIconAdapterOptions = IconAdapterOptions;
|
|
4
|
+
export declare const lucideIconAdapter: (icons: IconLibrary, options?: LucideIconAdapterOptions) => IconFieldIcon[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IconFieldIcon } from "../IconField.js";
|
|
2
|
+
import type { IconAdapterOptions, IconLibrary } from "./utils.js";
|
|
3
|
+
export type TablerIconAdapterOptions = IconAdapterOptions;
|
|
4
|
+
export declare const tablerIconAdapter: (icons: IconLibrary, options?: TablerIconAdapterOptions) => IconFieldIcon[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IconFieldIcon } from "../IconField.js";
|
|
2
|
+
export type IconAdapterOptions = {
|
|
3
|
+
exclude?: string[];
|
|
4
|
+
include?: string[];
|
|
5
|
+
};
|
|
6
|
+
export type IconLibrary = Record<string, unknown>;
|
|
7
|
+
export declare const createSvgIconAdapter: (icons: IconLibrary, options?: IconAdapterOptions) => IconFieldIcon[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const createSvgIconAdapter = (icons, options = {})=>{
|
|
2
|
+
const include = new Set(options.include ?? []);
|
|
3
|
+
const exclude = new Set(options.exclude ?? []);
|
|
4
|
+
return Object.entries(icons).flatMap(([name, icon])=>{
|
|
5
|
+
if (exclude.has(name) || include.size > 0 && !include.has(name)) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const iconNode = getIconNode(icon);
|
|
9
|
+
if (!iconNode) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
label: getLabel(name, icon),
|
|
15
|
+
name,
|
|
16
|
+
svg: iconNodeToSvg(iconNode),
|
|
17
|
+
value: name
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const getIconNode = (icon)=>{
|
|
23
|
+
if (!icon || typeof icon !== "object") {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const component = icon;
|
|
27
|
+
const rendered = component.render?.({}, null);
|
|
28
|
+
const iconNode = rendered?.props?.iconNode;
|
|
29
|
+
if (isIconNode(iconNode)) {
|
|
30
|
+
return iconNode;
|
|
31
|
+
}
|
|
32
|
+
if (isIconNode(rendered?.props?.children)) {
|
|
33
|
+
return rendered.props.children;
|
|
34
|
+
}
|
|
35
|
+
const renderedIconNode = reactElementToIconNode(rendered);
|
|
36
|
+
if (renderedIconNode) {
|
|
37
|
+
return renderedIconNode;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
const getLabel = (name, icon)=>{
|
|
42
|
+
if (icon && typeof icon === "object" && "displayName" in icon && typeof icon.displayName === "string") {
|
|
43
|
+
return icon.displayName;
|
|
44
|
+
}
|
|
45
|
+
return name;
|
|
46
|
+
};
|
|
47
|
+
const iconNodeToSvg = (iconNode)=>{
|
|
48
|
+
const children = iconNode.map(([tag, attrs])=>`<${tag}${attrsToString(attrs)} />`).join("");
|
|
49
|
+
return `<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">${children}</svg>`;
|
|
50
|
+
};
|
|
51
|
+
const reactElementToIconNode = (element)=>{
|
|
52
|
+
if (!element || element.type !== "svg") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const children = Array.isArray(element.props?.children) ? element.props.children : [
|
|
56
|
+
element.props?.children
|
|
57
|
+
];
|
|
58
|
+
const iconNode = children.filter(isReactElement).map((child)=>[
|
|
59
|
+
String(child.type),
|
|
60
|
+
child.props ?? {}
|
|
61
|
+
]).filter(([tag])=>tag !== "undefined");
|
|
62
|
+
return iconNode.length > 0 ? iconNode : undefined;
|
|
63
|
+
};
|
|
64
|
+
const attrsToString = (attrs)=>{
|
|
65
|
+
const attrString = Object.entries(attrs).filter(([key, value])=>key !== "key" && value !== null && value !== undefined).map(([key, value])=>` ${toKebabCase(key)}="${escapeHtml(String(value))}"`).join("");
|
|
66
|
+
return attrString;
|
|
67
|
+
};
|
|
68
|
+
const isIconNode = (value)=>Array.isArray(value) && value.every((item)=>{
|
|
69
|
+
if (!Array.isArray(item) || item.length !== 2) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const [tag, attrs] = item;
|
|
73
|
+
return typeof tag === "string" && Boolean(attrs) && typeof attrs === "object" && !Array.isArray(attrs);
|
|
74
|
+
});
|
|
75
|
+
const isReactElement = (value)=>value !== null && typeof value === "object" && "type" in value && "props" in value;
|
|
76
|
+
const toKebabCase = (value)=>value.replace(/[A-Z]/g, (match)=>`-${match.toLowerCase()}`);
|
|
77
|
+
const escapeHtml = (value)=>value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
package/package.json
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mvriu5/payload-icon-picker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Payload plugin introducing an icon picker into the CMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"import": "./
|
|
10
|
-
"types": "./
|
|
11
|
-
"default": "./
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./client": {
|
|
14
|
-
"import": "./
|
|
15
|
-
"types": "./
|
|
16
|
-
"default": "./
|
|
14
|
+
"import": "./dist/exports/client.js",
|
|
15
|
+
"types": "./dist/exports/client.d.ts",
|
|
16
|
+
"default": "./dist/exports/client.js"
|
|
17
17
|
},
|
|
18
|
-
"./
|
|
19
|
-
"import": "./
|
|
20
|
-
"types": "./
|
|
21
|
-
"default": "./
|
|
18
|
+
"./adapters/lucide": {
|
|
19
|
+
"import": "./dist/adapters/lucide.js",
|
|
20
|
+
"types": "./dist/adapters/lucide.d.ts",
|
|
21
|
+
"default": "./dist/adapters/lucide.js"
|
|
22
|
+
},
|
|
23
|
+
"./adapters/tabler": {
|
|
24
|
+
"import": "./dist/adapters/tabler.js",
|
|
25
|
+
"types": "./dist/adapters/tabler.d.ts",
|
|
26
|
+
"default": "./dist/adapters/tabler.js"
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
|
-
"main": "./
|
|
25
|
-
"types": "./
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
26
31
|
"files": [
|
|
27
32
|
"dist"
|
|
28
33
|
],
|
|
@@ -53,6 +58,7 @@
|
|
|
53
58
|
"@payloadcms/ui": "3.84.1",
|
|
54
59
|
"@playwright/test": "1.58.2",
|
|
55
60
|
"@swc/cli": "0.6.0",
|
|
61
|
+
"@tabler/icons-react": "^3.44.0",
|
|
56
62
|
"@types/node": "22.19.9",
|
|
57
63
|
"@types/react": "19.2.14",
|
|
58
64
|
"@types/react-dom": "19.2.3",
|
|
@@ -60,6 +66,7 @@
|
|
|
60
66
|
"cross-env": "^7.0.3",
|
|
61
67
|
"eslint": "^9.23.0",
|
|
62
68
|
"graphql": "^16.8.1",
|
|
69
|
+
"lucide-react": "^1.23.0",
|
|
63
70
|
"mongodb-memory-server": "10.1.4",
|
|
64
71
|
"next": "16.2.6",
|
|
65
72
|
"payload": "3.84.1",
|
|
@@ -92,10 +99,15 @@
|
|
|
92
99
|
"types": "./dist/exports/client.d.ts",
|
|
93
100
|
"default": "./dist/exports/client.js"
|
|
94
101
|
},
|
|
95
|
-
"./
|
|
96
|
-
"import": "./dist/
|
|
97
|
-
"types": "./dist/
|
|
98
|
-
"default": "./dist/
|
|
102
|
+
"./adapters/lucide": {
|
|
103
|
+
"import": "./dist/adapters/lucide.js",
|
|
104
|
+
"types": "./dist/adapters/lucide.d.ts",
|
|
105
|
+
"default": "./dist/adapters/lucide.js"
|
|
106
|
+
},
|
|
107
|
+
"./adapters/tabler": {
|
|
108
|
+
"import": "./dist/adapters/tabler.js",
|
|
109
|
+
"types": "./dist/adapters/tabler.d.ts",
|
|
110
|
+
"default": "./dist/adapters/tabler.js"
|
|
99
111
|
}
|
|
100
112
|
},
|
|
101
113
|
"main": "./dist/index.js",
|
|
@@ -128,7 +140,6 @@
|
|
|
128
140
|
},
|
|
129
141
|
"registry": "https://registry.npmjs.org/",
|
|
130
142
|
"dependencies": {
|
|
131
|
-
"knip": "^6.24.0"
|
|
132
|
-
"lucide-react": "^1.23.0"
|
|
143
|
+
"knip": "^6.24.0"
|
|
133
144
|
}
|
|
134
145
|
}
|