@pro-laico/payload-icons 0.0.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.
Files changed (119) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +9 -0
  3. package/dist/cache/index.d.ts +9 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/index.js +96 -0
  6. package/dist/cache/index.js.map +1 -0
  7. package/dist/collections/Icon.d.ts +14 -0
  8. package/dist/collections/Icon.d.ts.map +1 -0
  9. package/dist/collections/Icon.js +96 -0
  10. package/dist/collections/Icon.js.map +1 -0
  11. package/dist/collections/IconRequest.d.ts +30 -0
  12. package/dist/collections/IconRequest.d.ts.map +1 -0
  13. package/dist/collections/IconRequest.js +82 -0
  14. package/dist/collections/IconRequest.js.map +1 -0
  15. package/dist/collections/IconSet.d.ts +43 -0
  16. package/dist/collections/IconSet.d.ts.map +1 -0
  17. package/dist/collections/IconSet.js +168 -0
  18. package/dist/collections/IconSet.js.map +1 -0
  19. package/dist/components/Icon.d.ts +35 -0
  20. package/dist/components/Icon.d.ts.map +1 -0
  21. package/dist/components/Icon.js +56 -0
  22. package/dist/components/Icon.js.map +1 -0
  23. package/dist/components/admin/clearIconRequests.d.ts +14 -0
  24. package/dist/components/admin/clearIconRequests.d.ts.map +1 -0
  25. package/dist/components/admin/clearIconRequests.js +52 -0
  26. package/dist/components/admin/clearIconRequests.js.map +1 -0
  27. package/dist/components/admin/iconPreview.d.ts +7 -0
  28. package/dist/components/admin/iconPreview.d.ts.map +1 -0
  29. package/dist/components/admin/iconPreview.js +49 -0
  30. package/dist/components/admin/iconPreview.js.map +1 -0
  31. package/dist/components/admin/iconRowLabel.d.ts +8 -0
  32. package/dist/components/admin/iconRowLabel.d.ts.map +1 -0
  33. package/dist/components/admin/iconRowLabel.js +28 -0
  34. package/dist/components/admin/iconRowLabel.js.map +1 -0
  35. package/dist/components/admin/iconUsagePanel.client.d.ts +26 -0
  36. package/dist/components/admin/iconUsagePanel.client.d.ts.map +1 -0
  37. package/dist/components/admin/iconUsagePanel.client.js +290 -0
  38. package/dist/components/admin/iconUsagePanel.client.js.map +1 -0
  39. package/dist/components/admin/iconUsagePanel.d.ts +12 -0
  40. package/dist/components/admin/iconUsagePanel.d.ts.map +1 -0
  41. package/dist/components/admin/iconUsagePanel.js +68 -0
  42. package/dist/components/admin/iconUsagePanel.js.map +1 -0
  43. package/dist/hooks/formatSVG.d.ts +22 -0
  44. package/dist/hooks/formatSVG.d.ts.map +1 -0
  45. package/dist/hooks/formatSVG.js +270 -0
  46. package/dist/hooks/formatSVG.js.map +1 -0
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +10 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/lib/activeField.d.ts +24 -0
  52. package/dist/lib/activeField.d.ts.map +1 -0
  53. package/dist/lib/activeField.js +71 -0
  54. package/dist/lib/activeField.js.map +1 -0
  55. package/dist/lib/authenticated.d.ts +5 -0
  56. package/dist/lib/authenticated.d.ts.map +1 -0
  57. package/dist/lib/authenticated.js +4 -0
  58. package/dist/lib/authenticated.js.map +1 -0
  59. package/dist/lib/defaultAccess.d.ts +8 -0
  60. package/dist/lib/defaultAccess.d.ts.map +1 -0
  61. package/dist/lib/defaultAccess.js +6 -0
  62. package/dist/lib/defaultAccess.js.map +1 -0
  63. package/dist/lib/extractSVG.d.ts +3 -0
  64. package/dist/lib/extractSVG.d.ts.map +1 -0
  65. package/dist/lib/extractSVG.js +27 -0
  66. package/dist/lib/extractSVG.js.map +1 -0
  67. package/dist/lib/getPayloadClient.d.ts +22 -0
  68. package/dist/lib/getPayloadClient.d.ts.map +1 -0
  69. package/dist/lib/getPayloadClient.js +39 -0
  70. package/dist/lib/getPayloadClient.js.map +1 -0
  71. package/dist/lib/mergeHooks.d.ts +8 -0
  72. package/dist/lib/mergeHooks.d.ts.map +1 -0
  73. package/dist/lib/mergeHooks.js +19 -0
  74. package/dist/lib/mergeHooks.js.map +1 -0
  75. package/dist/lib/titleCase.d.ts +7 -0
  76. package/dist/lib/titleCase.d.ts.map +1 -0
  77. package/dist/lib/titleCase.js +17 -0
  78. package/dist/lib/titleCase.js.map +1 -0
  79. package/dist/plugin.d.ts +91 -0
  80. package/dist/plugin.d.ts.map +1 -0
  81. package/dist/plugin.js +72 -0
  82. package/dist/plugin.js.map +1 -0
  83. package/dist/scan/cli.d.ts +14 -0
  84. package/dist/scan/cli.d.ts.map +1 -0
  85. package/dist/scan/cli.js +114 -0
  86. package/dist/scan/cli.js.map +1 -0
  87. package/dist/scan/extract.d.ts +58 -0
  88. package/dist/scan/extract.d.ts.map +1 -0
  89. package/dist/scan/extract.js +286 -0
  90. package/dist/scan/extract.js.map +1 -0
  91. package/dist/scan/index.d.ts +81 -0
  92. package/dist/scan/index.d.ts.map +1 -0
  93. package/dist/scan/index.js +133 -0
  94. package/dist/scan/index.js.map +1 -0
  95. package/dist/scan/live.d.ts +20 -0
  96. package/dist/scan/live.d.ts.map +1 -0
  97. package/dist/scan/live.js +109 -0
  98. package/dist/scan/live.js.map +1 -0
  99. package/dist/scan/load.d.ts +28 -0
  100. package/dist/scan/load.d.ts.map +1 -0
  101. package/dist/scan/load.js +37 -0
  102. package/dist/scan/load.js.map +1 -0
  103. package/dist/scan/types.d.ts +29 -0
  104. package/dist/scan/types.d.ts.map +1 -0
  105. package/dist/scan/types.js +8 -0
  106. package/dist/scan/types.js.map +1 -0
  107. package/dist/types.d.ts +38 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +3 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/usage/recordMiss.d.ts +17 -0
  112. package/dist/usage/recordMiss.d.ts.map +1 -0
  113. package/dist/usage/recordMiss.js +60 -0
  114. package/dist/usage/recordMiss.js.map +1 -0
  115. package/dist/usage/trackIconMiss.d.ts +16 -0
  116. package/dist/usage/trackIconMiss.d.ts.map +1 -0
  117. package/dist/usage/trackIconMiss.js +43 -0
  118. package/dist/usage/trackIconMiss.js.map +1 -0
  119. package/package.json +106 -0
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Pro Laico LLC <contact@prolaico.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @pro-laico/payload-icons
2
+
3
+ SVG icons for [Payload CMS](https://payloadcms.com/) — an Icon upload collection that optimizes and sanitizes every SVG on save, plus a drop-in `<Icon name="…" />` server component.
4
+
5
+ ```bash
6
+ pnpm add @pro-laico/payload-icons
7
+ ```
8
+
9
+ **[Documentation →](https://payload-plugins.prolaico.com/docs/plugins/payload-icons)**
@@ -0,0 +1,9 @@
1
+ import 'server-only';
2
+ /** The SVG string for an icon name, resolved through the active set. Returns
3
+ * `undefined` when the name isn't in the active set. The single seam for
4
+ * rendering an icon yourself (the `<Icon>` component is this plus `extractSvg*`). */
5
+ export declare const getIconSvg: (name: string, draft?: boolean) => Promise<string | undefined>;
6
+ /** Dev-only diagnosis for an unresolved icon name: one `console.warn` per name per process, naming
7
+ * the cause — no active set / active set only a draft / name not in the set — with the fix. */
8
+ export declare const warnIconMissDev: (name: string, draft?: boolean) => Promise<void>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AA8DpB;;sFAEsF;AACtF,eAAO,MAAM,UAAU,GAAU,MAAM,MAAM,EAAE,eAAa,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAA0C,CAAA;AAKnI;gGACgG;AAChG,eAAO,MAAM,eAAe,GAAU,MAAM,MAAM,EAAE,eAAa,KAAG,OAAO,CAAC,IAAI,CA0B/E,CAAA"}
@@ -0,0 +1,96 @@
1
+ import "server-only";
2
+ import { cache } from "react";
3
+ import { getIconSetSlug, getPayloadClient } from "../lib/getPayloadClient.js";
4
+ /** The lane-correct active-set filter: published-lane reads also require `_status: 'published'`,
5
+ * but only when the collection actually has drafts (safe if `drafts: false`). */ const activeWhere = (payload, slug, draft)=>{
6
+ const hasDrafts = Boolean(payload.collections?.[slug]?.config?.versions?.drafts);
7
+ return !draft && hasDrafts ? {
8
+ and: [
9
+ {
10
+ active: {
11
+ equals: true
12
+ }
13
+ },
14
+ {
15
+ _status: {
16
+ equals: 'published'
17
+ }
18
+ }
19
+ ]
20
+ } : {
21
+ active: {
22
+ equals: true
23
+ }
24
+ };
25
+ };
26
+ /**
27
+ * The active icon set's `name → svgString` map, resolved in a SINGLE query and
28
+ * memoized per request via React `cache()`. Finds the set with `active: true`
29
+ * and populates each row's icon `svgString` in one go (depth 1 + a scoped
30
+ * `populate`), so a page with K icons costs one query, not K+1.
31
+ *
32
+ * On the published frontend (`draft: false`) it filters `_status: 'published'`,
33
+ * so it reads the published-lane active set — a set activated only in the draft
34
+ * lane (staged but not published) resolves to nothing, never leaking to prod.
35
+ * In draft mode it reads the draft-lane active set. The `_status` filter is only
36
+ * applied when the collection actually has drafts (safe if `drafts: false`).
37
+ */ const getActiveIconSet = cache(async (draft)=>{
38
+ const payload = await getPayloadClient();
39
+ // Read the slug AFTER getPayloadClient(): resolving the config applies the plugin, which stashes it.
40
+ const slug = getIconSetSlug();
41
+ const set = await payload.find({
42
+ collection: slug,
43
+ where: activeWhere(payload, slug, draft),
44
+ limit: 1,
45
+ depth: 1,
46
+ draft,
47
+ pagination: false,
48
+ overrideAccess: true,
49
+ select: {
50
+ iconsArray: true
51
+ },
52
+ // Scope the populated icon docs to just the svgString we inline.
53
+ populate: {
54
+ icon: {
55
+ svgString: true
56
+ }
57
+ }
58
+ }).then((res)=>res.docs[0] || null);
59
+ if (!set) return {};
60
+ const map = {};
61
+ for (const row of set.iconsArray ?? []){
62
+ const svg = row?.icon && typeof row.icon === 'object' ? row.icon.svgString : undefined;
63
+ if (row?.name && svg) map[row.name] = svg;
64
+ }
65
+ return map;
66
+ });
67
+ /** The SVG string for an icon name, resolved through the active set. Returns
68
+ * `undefined` when the name isn't in the active set. The single seam for
69
+ * rendering an icon yourself (the `<Icon>` component is this plus `extractSvg*`). */ export const getIconSvg = async (name, draft = false)=>(await getActiveIconSet(draft))[name];
70
+ /** Names already warned about, so each miss logs once per process. */ const warnedMisses = new Set();
71
+ /** Dev-only diagnosis for an unresolved icon name: one `console.warn` per name per process, naming
72
+ * the cause — no active set / active set only a draft / name not in the set — with the fix. */ export const warnIconMissDev = async (name, draft = false)=>{
73
+ if (process.env.NODE_ENV === 'production' || warnedMisses.has(name)) return;
74
+ warnedMisses.add(name);
75
+ try {
76
+ const payload = await getPayloadClient();
77
+ const slug = getIconSetSlug();
78
+ const activeSetExists = async (d)=>{
79
+ const find = {
80
+ collection: slug,
81
+ where: activeWhere(payload, slug, d),
82
+ limit: 1,
83
+ depth: 0,
84
+ draft: d,
85
+ overrideAccess: true
86
+ };
87
+ return (await payload.find(find)).docs.length > 0;
88
+ };
89
+ const cause = await activeSetExists(draft) ? `name '${name}' not in the active set — add it to the set's Icons array` : await activeSetExists(true) ? 'active set exists only as a draft — publish it' : 'no active icon set — activate one';
90
+ console.warn(`[payload-icons] <Icon name="${name}"> did not resolve: ${cause}`);
91
+ } catch {
92
+ // Diagnostics only — never surface failures into render.
93
+ }
94
+ };
95
+
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cache/index.ts"],"sourcesContent":["import 'server-only'\n\nimport { cache } from 'react'\nimport type { CollectionSlug, Payload, Where } from 'payload'\n\nimport { getIconSetSlug, getPayloadClient } from '../lib/getPayloadClient'\n\n/** A `name → svgString` map for the active set's icons. */\ntype IconSetMap = Record<string, string>\n\n/** The lane-correct active-set filter: published-lane reads also require `_status: 'published'`,\n * but only when the collection actually has drafts (safe if `drafts: false`). */\nconst activeWhere = (payload: Payload, slug: string, draft: boolean): Where => {\n const hasDrafts = Boolean(\n (payload.collections as Record<string, { config?: { versions?: { drafts?: unknown } } }>)?.[slug]?.config?.versions?.drafts,\n )\n return !draft && hasDrafts ? { and: [{ active: { equals: true } }, { _status: { equals: 'published' } }] } : { active: { equals: true } }\n}\n\n/**\n * The active icon set's `name → svgString` map, resolved in a SINGLE query and\n * memoized per request via React `cache()`. Finds the set with `active: true`\n * and populates each row's icon `svgString` in one go (depth 1 + a scoped\n * `populate`), so a page with K icons costs one query, not K+1.\n *\n * On the published frontend (`draft: false`) it filters `_status: 'published'`,\n * so it reads the published-lane active set — a set activated only in the draft\n * lane (staged but not published) resolves to nothing, never leaking to prod.\n * In draft mode it reads the draft-lane active set. The `_status` filter is only\n * applied when the collection actually has drafts (safe if `drafts: false`).\n */\nconst getActiveIconSet = cache(async (draft: boolean): Promise<IconSetMap> => {\n const payload = await getPayloadClient()\n // Read the slug AFTER getPayloadClient(): resolving the config applies the plugin, which stashes it.\n const slug = getIconSetSlug()\n\n const set = (await payload\n .find({\n collection: slug as CollectionSlug,\n where: activeWhere(payload, slug, draft),\n limit: 1,\n depth: 1,\n draft,\n pagination: false,\n overrideAccess: true,\n select: { iconsArray: true },\n // Scope the populated icon docs to just the svgString we inline.\n populate: { icon: { svgString: true } },\n })\n .then((res) => res.docs[0] || null)) as {\n iconsArray?: { name?: string | null; icon?: { svgString?: string | null } | string | number | null }[]\n } | null\n\n if (!set) return {}\n const map: IconSetMap = {}\n for (const row of set.iconsArray ?? []) {\n const svg = row?.icon && typeof row.icon === 'object' ? row.icon.svgString : undefined\n if (row?.name && svg) map[row.name] = svg\n }\n return map\n})\n\n/** The SVG string for an icon name, resolved through the active set. Returns\n * `undefined` when the name isn't in the active set. The single seam for\n * rendering an icon yourself (the `<Icon>` component is this plus `extractSvg*`). */\nexport const getIconSvg = async (name: string, draft = false): Promise<string | undefined> => (await getActiveIconSet(draft))[name]\n\n/** Names already warned about, so each miss logs once per process. */\nconst warnedMisses = new Set<string>()\n\n/** Dev-only diagnosis for an unresolved icon name: one `console.warn` per name per process, naming\n * the cause — no active set / active set only a draft / name not in the set — with the fix. */\nexport const warnIconMissDev = async (name: string, draft = false): Promise<void> => {\n if (process.env.NODE_ENV === 'production' || warnedMisses.has(name)) return\n warnedMisses.add(name)\n try {\n const payload = await getPayloadClient()\n const slug = getIconSetSlug()\n const activeSetExists = async (d: boolean): Promise<boolean> => {\n const find = {\n collection: slug as CollectionSlug,\n where: activeWhere(payload, slug, d),\n limit: 1,\n depth: 0,\n draft: d,\n overrideAccess: true,\n }\n return (await payload.find(find)).docs.length > 0\n }\n const cause = (await activeSetExists(draft))\n ? `name '${name}' not in the active set — add it to the set's Icons array`\n : (await activeSetExists(true))\n ? 'active set exists only as a draft — publish it'\n : 'no active icon set — activate one'\n console.warn(`[payload-icons] <Icon name=\"${name}\"> did not resolve: ${cause}`)\n } catch {\n // Diagnostics only — never surface failures into render.\n }\n}\n"],"names":["cache","getIconSetSlug","getPayloadClient","activeWhere","payload","slug","draft","hasDrafts","Boolean","collections","config","versions","drafts","and","active","equals","_status","getActiveIconSet","set","find","collection","where","limit","depth","pagination","overrideAccess","select","iconsArray","populate","icon","svgString","then","res","docs","map","row","svg","undefined","name","getIconSvg","warnedMisses","Set","warnIconMissDev","process","env","NODE_ENV","has","add","activeSetExists","d","length","cause","console","warn"],"mappings":"AAAA,OAAO,cAAa;AAEpB,SAASA,KAAK,QAAQ,QAAO;AAG7B,SAASC,cAAc,EAAEC,gBAAgB,QAAQ,6BAAyB;AAK1E;gFACgF,GAChF,MAAMC,cAAc,CAACC,SAAkBC,MAAcC;IACnD,MAAMC,YAAYC,QACfJ,QAAQK,WAAW,EAAuE,CAACJ,KAAK,EAAEK,QAAQC,UAAUC;IAEvH,OAAO,CAACN,SAASC,YAAY;QAAEM,KAAK;YAAC;gBAAEC,QAAQ;oBAAEC,QAAQ;gBAAK;YAAE;YAAG;gBAAEC,SAAS;oBAAED,QAAQ;gBAAY;YAAE;SAAE;IAAC,IAAI;QAAED,QAAQ;YAAEC,QAAQ;QAAK;IAAE;AAC1I;AAEA;;;;;;;;;;;CAWC,GACD,MAAME,mBAAmBjB,MAAM,OAAOM;IACpC,MAAMF,UAAU,MAAMF;IACtB,qGAAqG;IACrG,MAAMG,OAAOJ;IAEb,MAAMiB,MAAO,MAAMd,QAChBe,IAAI,CAAC;QACJC,YAAYf;QACZgB,OAAOlB,YAAYC,SAASC,MAAMC;QAClCgB,OAAO;QACPC,OAAO;QACPjB;QACAkB,YAAY;QACZC,gBAAgB;QAChBC,QAAQ;YAAEC,YAAY;QAAK;QAC3B,iEAAiE;QACjEC,UAAU;YAAEC,MAAM;gBAAEC,WAAW;YAAK;QAAE;IACxC,GACCC,IAAI,CAAC,CAACC,MAAQA,IAAIC,IAAI,CAAC,EAAE,IAAI;IAIhC,IAAI,CAACf,KAAK,OAAO,CAAC;IAClB,MAAMgB,MAAkB,CAAC;IACzB,KAAK,MAAMC,OAAOjB,IAAIS,UAAU,IAAI,EAAE,CAAE;QACtC,MAAMS,MAAMD,KAAKN,QAAQ,OAAOM,IAAIN,IAAI,KAAK,WAAWM,IAAIN,IAAI,CAACC,SAAS,GAAGO;QAC7E,IAAIF,KAAKG,QAAQF,KAAKF,GAAG,CAACC,IAAIG,IAAI,CAAC,GAAGF;IACxC;IACA,OAAOF;AACT;AAEA;;oFAEoF,GACpF,OAAO,MAAMK,aAAa,OAAOD,MAAchC,QAAQ,KAAK,GAAkC,AAAC,CAAA,MAAMW,iBAAiBX,MAAK,CAAE,CAACgC,KAAK,CAAA;AAEnI,oEAAoE,GACpE,MAAME,eAAe,IAAIC;AAEzB;8FAC8F,GAC9F,OAAO,MAAMC,kBAAkB,OAAOJ,MAAchC,QAAQ,KAAK;IAC/D,IAAIqC,QAAQC,GAAG,CAACC,QAAQ,KAAK,gBAAgBL,aAAaM,GAAG,CAACR,OAAO;IACrEE,aAAaO,GAAG,CAACT;IACjB,IAAI;QACF,MAAMlC,UAAU,MAAMF;QACtB,MAAMG,OAAOJ;QACb,MAAM+C,kBAAkB,OAAOC;YAC7B,MAAM9B,OAAO;gBACXC,YAAYf;gBACZgB,OAAOlB,YAAYC,SAASC,MAAM4C;gBAClC3B,OAAO;gBACPC,OAAO;gBACPjB,OAAO2C;gBACPxB,gBAAgB;YAClB;YACA,OAAO,AAAC,CAAA,MAAMrB,QAAQe,IAAI,CAACA,KAAI,EAAGc,IAAI,CAACiB,MAAM,GAAG;QAClD;QACA,MAAMC,QAAQ,AAAC,MAAMH,gBAAgB1C,SACjC,CAAC,MAAM,EAAEgC,KAAK,yDAAyD,CAAC,GACxE,AAAC,MAAMU,gBAAgB,QACrB,mDACA;QACNI,QAAQC,IAAI,CAAC,CAAC,4BAA4B,EAAEf,KAAK,oBAAoB,EAAEa,OAAO;IAChF,EAAE,OAAM;IACN,yDAAyD;IAC3D;AACF,EAAC"}
@@ -0,0 +1,14 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ import type { IconCollectionOverrides } from '../types';
3
+ /** The default slug for the icon collection. */
4
+ export declare const ICON_SLUG = "icon";
5
+ /**
6
+ * Build the `Icon` upload collection: accepts `image/svg+xml` uploads, optimizes + sanitizes
7
+ * each SVG on `beforeChange` (`formatSVGHook`), and stores the result as `svgString` for inline
8
+ * frontend rendering. Public read by default; logged-in-admin writes.
9
+ *
10
+ * Pass {@link IconCollectionOverrides} to append fields/hooks, override access, or rename the slug —
11
+ * additions stack on top of the defaults.
12
+ */
13
+ export declare const Icon: (options?: IconCollectionOverrides) => CollectionConfig;
14
+ //# sourceMappingURL=Icon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../src/collections/Icon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAEvD,gDAAgD;AAChD,eAAO,MAAM,SAAS,SAAS,CAAA;AAM/B;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,GAAI,UAAS,uBAA4B,KAAG,gBA0C5D,CAAA"}
@@ -0,0 +1,96 @@
1
+ import { formatSVGHook } from "../hooks/formatSVG.js";
2
+ import { defaultAdminAccess, defaultReadAccess } from "../lib/defaultAccess.js";
3
+ import { mergeHooks } from "../lib/mergeHooks.js";
4
+ /** The default slug for the icon collection. */ export const ICON_SLUG = 'icon';
5
+ /** Admin import-map paths for the theme-aware inline previews (the stored file `<img>`s as black — invisible on the dark theme). */ const IconPreviewFieldPath = '@pro-laico/payload-icons/admin/iconPreview#IconPreviewField';
6
+ const IconPreviewCellPath = '@pro-laico/payload-icons/admin/iconPreview#IconPreviewCell';
7
+ /**
8
+ * Build the `Icon` upload collection: accepts `image/svg+xml` uploads, optimizes + sanitizes
9
+ * each SVG on `beforeChange` (`formatSVGHook`), and stores the result as `svgString` for inline
10
+ * frontend rendering. Public read by default; logged-in-admin writes.
11
+ *
12
+ * Pass {@link IconCollectionOverrides} to append fields/hooks, override access, or rename the slug —
13
+ * additions stack on top of the defaults.
14
+ */ export const Icon = (options = {})=>{
15
+ const { slug = ICON_SLUG, adminGroup = 'Assets', access, fields = [], hooks, upload } = options;
16
+ return {
17
+ slug,
18
+ labels: {
19
+ singular: 'Icon',
20
+ plural: 'Icons'
21
+ },
22
+ access: {
23
+ read: access?.read ?? defaultReadAccess,
24
+ create: access?.create ?? defaultAdminAccess,
25
+ update: access?.update ?? defaultAdminAccess,
26
+ delete: access?.delete ?? defaultAdminAccess
27
+ },
28
+ admin: {
29
+ group: adminGroup,
30
+ useAsTitle: 'filename',
31
+ defaultColumns: [
32
+ 'filename',
33
+ 'svgString',
34
+ 'filesize',
35
+ 'updatedAt'
36
+ ]
37
+ },
38
+ // Payload blocks SVG uploads by default (they're on its restricted-types list). This is an
39
+ // SVG-only collection and `formatSVGHook` sanitizes every file (scripts / on* handlers /
40
+ // javascript: URLs stripped) before storage, so we opt in. `mimeTypes` still scopes uploads
41
+ // to SVG, and the opt-in stays overridable via `options.upload`.
42
+ upload: {
43
+ mimeTypes: [
44
+ 'image/svg+xml'
45
+ ],
46
+ allowRestrictedFileTypes: true,
47
+ ...upload
48
+ },
49
+ fields: [
50
+ {
51
+ name: 'iconPreview',
52
+ type: 'ui',
53
+ admin: {
54
+ components: {
55
+ Field: IconPreviewFieldPath
56
+ },
57
+ condition: (data)=>Boolean(data?.svgString)
58
+ }
59
+ },
60
+ {
61
+ name: 'optimized',
62
+ type: 'text',
63
+ admin: {
64
+ readOnly: true,
65
+ condition: (data)=>Boolean(data?.optimized)
66
+ }
67
+ },
68
+ {
69
+ name: 'svgString',
70
+ type: 'code',
71
+ admin: {
72
+ components: {
73
+ Cell: IconPreviewCellPath
74
+ },
75
+ language: 'xml',
76
+ // Read-only: a direct edit would bypass formatSVGHook's sanitization (only runs on file
77
+ // uploads) and the string is later inlined via dangerouslySetInnerHTML — re-upload to change.
78
+ readOnly: true,
79
+ condition: (data)=>Boolean(data?.svgString),
80
+ editorOptions: {
81
+ wordWrap: 'off',
82
+ scrollBeyondLastLine: false
83
+ }
84
+ }
85
+ },
86
+ ...fields
87
+ ],
88
+ hooks: mergeHooks({
89
+ beforeChange: [
90
+ formatSVGHook
91
+ ]
92
+ }, hooks)
93
+ };
94
+ };
95
+
96
+ //# sourceMappingURL=Icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/Icon.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport { formatSVGHook } from '../hooks/formatSVG'\nimport { defaultAdminAccess, defaultReadAccess } from '../lib/defaultAccess'\nimport { mergeHooks } from '../lib/mergeHooks'\nimport type { IconCollectionOverrides } from '../types'\n\n/** The default slug for the icon collection. */\nexport const ICON_SLUG = 'icon'\n\n/** Admin import-map paths for the theme-aware inline previews (the stored file `<img>`s as black — invisible on the dark theme). */\nconst IconPreviewFieldPath = '@pro-laico/payload-icons/admin/iconPreview#IconPreviewField'\nconst IconPreviewCellPath = '@pro-laico/payload-icons/admin/iconPreview#IconPreviewCell'\n\n/**\n * Build the `Icon` upload collection: accepts `image/svg+xml` uploads, optimizes + sanitizes\n * each SVG on `beforeChange` (`formatSVGHook`), and stores the result as `svgString` for inline\n * frontend rendering. Public read by default; logged-in-admin writes.\n *\n * Pass {@link IconCollectionOverrides} to append fields/hooks, override access, or rename the slug —\n * additions stack on top of the defaults.\n */\nexport const Icon = (options: IconCollectionOverrides = {}): CollectionConfig => {\n const { slug = ICON_SLUG, adminGroup = 'Assets', access, fields = [], hooks, upload } = options\n\n return {\n slug,\n labels: { singular: 'Icon', plural: 'Icons' },\n access: {\n read: access?.read ?? defaultReadAccess,\n create: access?.create ?? defaultAdminAccess,\n update: access?.update ?? defaultAdminAccess,\n delete: access?.delete ?? defaultAdminAccess,\n },\n admin: { group: adminGroup, useAsTitle: 'filename', defaultColumns: ['filename', 'svgString', 'filesize', 'updatedAt'] },\n // Payload blocks SVG uploads by default (they're on its restricted-types list). This is an\n // SVG-only collection and `formatSVGHook` sanitizes every file (scripts / on* handlers /\n // javascript: URLs stripped) before storage, so we opt in. `mimeTypes` still scopes uploads\n // to SVG, and the opt-in stays overridable via `options.upload`.\n upload: { mimeTypes: ['image/svg+xml'], allowRestrictedFileTypes: true, ...upload },\n fields: [\n {\n name: 'iconPreview',\n type: 'ui',\n admin: { components: { Field: IconPreviewFieldPath }, condition: (data) => Boolean(data?.svgString) },\n },\n { name: 'optimized', type: 'text', admin: { readOnly: true, condition: (data) => Boolean(data?.optimized) } },\n {\n name: 'svgString',\n type: 'code',\n admin: {\n components: { Cell: IconPreviewCellPath },\n language: 'xml',\n // Read-only: a direct edit would bypass formatSVGHook's sanitization (only runs on file\n // uploads) and the string is later inlined via dangerouslySetInnerHTML — re-upload to change.\n readOnly: true,\n condition: (data) => Boolean(data?.svgString),\n editorOptions: { wordWrap: 'off', scrollBeyondLastLine: false },\n },\n },\n ...fields,\n ],\n hooks: mergeHooks({ beforeChange: [formatSVGHook] }, hooks),\n }\n}\n"],"names":["formatSVGHook","defaultAdminAccess","defaultReadAccess","mergeHooks","ICON_SLUG","IconPreviewFieldPath","IconPreviewCellPath","Icon","options","slug","adminGroup","access","fields","hooks","upload","labels","singular","plural","read","create","update","delete","admin","group","useAsTitle","defaultColumns","mimeTypes","allowRestrictedFileTypes","name","type","components","Field","condition","data","Boolean","svgString","readOnly","optimized","Cell","language","editorOptions","wordWrap","scrollBeyondLastLine","beforeChange"],"mappings":"AACA,SAASA,aAAa,QAAQ,wBAAoB;AAClD,SAASC,kBAAkB,EAAEC,iBAAiB,QAAQ,0BAAsB;AAC5E,SAASC,UAAU,QAAQ,uBAAmB;AAG9C,8CAA8C,GAC9C,OAAO,MAAMC,YAAY,OAAM;AAE/B,kIAAkI,GAClI,MAAMC,uBAAuB;AAC7B,MAAMC,sBAAsB;AAE5B;;;;;;;CAOC,GACD,OAAO,MAAMC,OAAO,CAACC,UAAmC,CAAC,CAAC;IACxD,MAAM,EAAEC,OAAOL,SAAS,EAAEM,aAAa,QAAQ,EAAEC,MAAM,EAAEC,SAAS,EAAE,EAAEC,KAAK,EAAEC,MAAM,EAAE,GAAGN;IAExF,OAAO;QACLC;QACAM,QAAQ;YAAEC,UAAU;YAAQC,QAAQ;QAAQ;QAC5CN,QAAQ;YACNO,MAAMP,QAAQO,QAAQhB;YACtBiB,QAAQR,QAAQQ,UAAUlB;YAC1BmB,QAAQT,QAAQS,UAAUnB;YAC1BoB,QAAQV,QAAQU,UAAUpB;QAC5B;QACAqB,OAAO;YAAEC,OAAOb;YAAYc,YAAY;YAAYC,gBAAgB;gBAAC;gBAAY;gBAAa;gBAAY;aAAY;QAAC;QACvH,2FAA2F;QAC3F,yFAAyF;QACzF,4FAA4F;QAC5F,iEAAiE;QACjEX,QAAQ;YAAEY,WAAW;gBAAC;aAAgB;YAAEC,0BAA0B;YAAM,GAAGb,MAAM;QAAC;QAClFF,QAAQ;YACN;gBACEgB,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBAAEQ,YAAY;wBAAEC,OAAO1B;oBAAqB;oBAAG2B,WAAW,CAACC,OAASC,QAAQD,MAAME;gBAAW;YACtG;YACA;gBAAEP,MAAM;gBAAaC,MAAM;gBAAQP,OAAO;oBAAEc,UAAU;oBAAMJ,WAAW,CAACC,OAASC,QAAQD,MAAMI;gBAAW;YAAE;YAC5G;gBACET,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLQ,YAAY;wBAAEQ,MAAMhC;oBAAoB;oBACxCiC,UAAU;oBACV,wFAAwF;oBACxF,8FAA8F;oBAC9FH,UAAU;oBACVJ,WAAW,CAACC,OAASC,QAAQD,MAAME;oBACnCK,eAAe;wBAAEC,UAAU;wBAAOC,sBAAsB;oBAAM;gBAChE;YACF;eACG9B;SACJ;QACDC,OAAOV,WAAW;YAAEwC,cAAc;gBAAC3C;aAAc;QAAC,GAAGa;IACvD;AACF,EAAC"}
@@ -0,0 +1,30 @@
1
+ import type { CollectionConfig, Field } from 'payload';
2
+ /** Slug of the runtime icon-request diagnostic collection. */
3
+ export declare const ICON_REQUEST_SLUG = "iconRequest";
4
+ /**
5
+ * Overrides for {@link createIconRequestCollection}. All additive on top of the
6
+ * built-in `name` / `count` / `firstRequestedAt` / `lastRequestedAt` fields.
7
+ */
8
+ export interface IconRequestCollectionOverrides {
9
+ /** Override the `admin.group` sidebar label. @default 'Sets' */
10
+ group?: string;
11
+ /** Extra fields appended after the built-ins. */
12
+ fields?: Field[];
13
+ /** Additional Payload hooks merged onto the collection. */
14
+ hooks?: CollectionConfig['hooks'];
15
+ }
16
+ /**
17
+ * Diagnostic collection that records icon names requested at runtime which did
18
+ * NOT resolve to an icon in the active set — the live counterpart to the
19
+ * build-time usage manifest. Populated (throttled, fire-and-forget) by the
20
+ * `<Icon>` server component when `iconsPlugin({ trackRequests: true })` is set,
21
+ * and surfaced in the IconSet "Requested icons" panel.
22
+ *
23
+ * Unlike the static scan, this captures DYNAMIC names (`<Icon name={slug} />`)
24
+ * and genuine production misses — exactly the names a static pass can't see.
25
+ *
26
+ * Rows are written by the recorder via `overrideAccess`; admin access is
27
+ * read/update/delete for authenticated users (it's data you prune, not author).
28
+ */
29
+ export declare const createIconRequestCollection: (opts?: IconRequestCollectionOverrides) => CollectionConfig;
30
+ //# sourceMappingURL=IconRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconRequest.d.ts","sourceRoot":"","sources":["../../src/collections/IconRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAItD,8DAA8D;AAC9D,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAM,8BAAmC,KAAG,gBA4BvF,CAAA"}
@@ -0,0 +1,82 @@
1
+ import { authd } from "../lib/authenticated.js";
2
+ /** Slug of the runtime icon-request diagnostic collection. */ export const ICON_REQUEST_SLUG = 'iconRequest';
3
+ /**
4
+ * Diagnostic collection that records icon names requested at runtime which did
5
+ * NOT resolve to an icon in the active set — the live counterpart to the
6
+ * build-time usage manifest. Populated (throttled, fire-and-forget) by the
7
+ * `<Icon>` server component when `iconsPlugin({ trackRequests: true })` is set,
8
+ * and surfaced in the IconSet "Requested icons" panel.
9
+ *
10
+ * Unlike the static scan, this captures DYNAMIC names (`<Icon name={slug} />`)
11
+ * and genuine production misses — exactly the names a static pass can't see.
12
+ *
13
+ * Rows are written by the recorder via `overrideAccess`; admin access is
14
+ * read/update/delete for authenticated users (it's data you prune, not author).
15
+ */ export const createIconRequestCollection = (opts = {})=>{
16
+ const { group = 'Sets', fields: extraFields = [], hooks } = opts;
17
+ return {
18
+ slug: ICON_REQUEST_SLUG,
19
+ access: {
20
+ create: authd,
21
+ delete: authd,
22
+ read: authd,
23
+ update: authd
24
+ },
25
+ admin: {
26
+ group,
27
+ useAsTitle: 'name',
28
+ defaultColumns: [
29
+ 'name',
30
+ 'count',
31
+ 'lastRequestedAt'
32
+ ],
33
+ // Hidden from the sidebar nav — it's a diagnostic feed surfaced through the
34
+ // IconSet "Requested icons" panel (which reads/clears it via the API), not
35
+ // a collection editors browse directly. `hidden` only affects nav/admin
36
+ // visibility; the REST + local API still work, so the panel and recorder
37
+ // are unaffected.
38
+ hidden: true,
39
+ description: 'Icon names requested at runtime that did not resolve to an icon in the active set. Populated when iconsPlugin({ trackRequests: true }) is set; compare against the IconSet usage panel.'
40
+ },
41
+ fields: [
42
+ {
43
+ name: 'name',
44
+ type: 'text',
45
+ required: true,
46
+ unique: true,
47
+ index: true,
48
+ admin: {
49
+ readOnly: true
50
+ }
51
+ },
52
+ {
53
+ name: 'count',
54
+ type: 'number',
55
+ defaultValue: 1,
56
+ admin: {
57
+ readOnly: true
58
+ }
59
+ },
60
+ {
61
+ name: 'firstRequestedAt',
62
+ type: 'date',
63
+ admin: {
64
+ readOnly: true
65
+ }
66
+ },
67
+ {
68
+ name: 'lastRequestedAt',
69
+ type: 'date',
70
+ admin: {
71
+ readOnly: true
72
+ }
73
+ },
74
+ ...extraFields
75
+ ],
76
+ ...hooks ? {
77
+ hooks
78
+ } : {}
79
+ };
80
+ };
81
+
82
+ //# sourceMappingURL=IconRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/IconRequest.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nimport { authd } from '../lib/authenticated'\n\n/** Slug of the runtime icon-request diagnostic collection. */\nexport const ICON_REQUEST_SLUG = 'iconRequest'\n\n/**\n * Overrides for {@link createIconRequestCollection}. All additive on top of the\n * built-in `name` / `count` / `firstRequestedAt` / `lastRequestedAt` fields.\n */\nexport interface IconRequestCollectionOverrides {\n /** Override the `admin.group` sidebar label. @default 'Sets' */\n group?: string\n /** Extra fields appended after the built-ins. */\n fields?: Field[]\n /** Additional Payload hooks merged onto the collection. */\n hooks?: CollectionConfig['hooks']\n}\n\n/**\n * Diagnostic collection that records icon names requested at runtime which did\n * NOT resolve to an icon in the active set — the live counterpart to the\n * build-time usage manifest. Populated (throttled, fire-and-forget) by the\n * `<Icon>` server component when `iconsPlugin({ trackRequests: true })` is set,\n * and surfaced in the IconSet \"Requested icons\" panel.\n *\n * Unlike the static scan, this captures DYNAMIC names (`<Icon name={slug} />`)\n * and genuine production misses — exactly the names a static pass can't see.\n *\n * Rows are written by the recorder via `overrideAccess`; admin access is\n * read/update/delete for authenticated users (it's data you prune, not author).\n */\nexport const createIconRequestCollection = (opts: IconRequestCollectionOverrides = {}): CollectionConfig => {\n const { group = 'Sets', fields: extraFields = [], hooks } = opts\n\n return {\n slug: ICON_REQUEST_SLUG,\n access: { create: authd, delete: authd, read: authd, update: authd },\n admin: {\n group,\n useAsTitle: 'name',\n defaultColumns: ['name', 'count', 'lastRequestedAt'],\n // Hidden from the sidebar nav — it's a diagnostic feed surfaced through the\n // IconSet \"Requested icons\" panel (which reads/clears it via the API), not\n // a collection editors browse directly. `hidden` only affects nav/admin\n // visibility; the REST + local API still work, so the panel and recorder\n // are unaffected.\n hidden: true,\n description:\n 'Icon names requested at runtime that did not resolve to an icon in the active set. Populated when iconsPlugin({ trackRequests: true }) is set; compare against the IconSet usage panel.',\n },\n fields: [\n { name: 'name', type: 'text', required: true, unique: true, index: true, admin: { readOnly: true } },\n { name: 'count', type: 'number', defaultValue: 1, admin: { readOnly: true } },\n { name: 'firstRequestedAt', type: 'date', admin: { readOnly: true } },\n { name: 'lastRequestedAt', type: 'date', admin: { readOnly: true } },\n ...extraFields,\n ],\n ...(hooks ? { hooks } : {}),\n }\n}\n"],"names":["authd","ICON_REQUEST_SLUG","createIconRequestCollection","opts","group","fields","extraFields","hooks","slug","access","create","delete","read","update","admin","useAsTitle","defaultColumns","hidden","description","name","type","required","unique","index","readOnly","defaultValue"],"mappings":"AAEA,SAASA,KAAK,QAAQ,0BAAsB;AAE5C,4DAA4D,GAC5D,OAAO,MAAMC,oBAAoB,cAAa;AAe9C;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,8BAA8B,CAACC,OAAuC,CAAC,CAAC;IACnF,MAAM,EAAEC,QAAQ,MAAM,EAAEC,QAAQC,cAAc,EAAE,EAAEC,KAAK,EAAE,GAAGJ;IAE5D,OAAO;QACLK,MAAMP;QACNQ,QAAQ;YAAEC,QAAQV;YAAOW,QAAQX;YAAOY,MAAMZ;YAAOa,QAAQb;QAAM;QACnEc,OAAO;YACLV;YACAW,YAAY;YACZC,gBAAgB;gBAAC;gBAAQ;gBAAS;aAAkB;YACpD,4EAA4E;YAC5E,2EAA2E;YAC3E,wEAAwE;YACxE,yEAAyE;YACzE,kBAAkB;YAClBC,QAAQ;YACRC,aACE;QACJ;QACAb,QAAQ;YACN;gBAAEc,MAAM;gBAAQC,MAAM;gBAAQC,UAAU;gBAAMC,QAAQ;gBAAMC,OAAO;gBAAMT,OAAO;oBAAEU,UAAU;gBAAK;YAAE;YACnG;gBAAEL,MAAM;gBAASC,MAAM;gBAAUK,cAAc;gBAAGX,OAAO;oBAAEU,UAAU;gBAAK;YAAE;YAC5E;gBAAEL,MAAM;gBAAoBC,MAAM;gBAAQN,OAAO;oBAAEU,UAAU;gBAAK;YAAE;YACpE;gBAAEL,MAAM;gBAAmBC,MAAM;gBAAQN,OAAO;oBAAEU,UAAU;gBAAK;YAAE;eAChElB;SACJ;QACD,GAAIC,QAAQ;YAAEA;QAAM,IAAI,CAAC,CAAC;IAC5B;AACF,EAAC"}
@@ -0,0 +1,43 @@
1
+ import type { CollectionConfig, Field, PayloadRequest } from 'payload';
2
+ /** The default slug for the icon-set collection. */
3
+ export declare const ICON_SET_SLUG = "iconSet";
4
+ /**
5
+ * Overrides for the `iconSet` collection — a named, ordered `name → icon` mapping
6
+ * into the shared `icon` pool. One set is `active` at a time; the frontend renders
7
+ * it (see {@link enforceSingleActive}).
8
+ */
9
+ export interface IconSetCollectionOverrides {
10
+ /** Slug for the icon-set collection. @default 'iconSet' */
11
+ slug?: string;
12
+ /**
13
+ * Live-preview URL generator. When provided, wires both `admin.preview`
14
+ * (legacy iframe) and `admin.livePreview.url`. Omitted by default.
15
+ */
16
+ livePreviewUrl?: (args: {
17
+ data: Record<string, unknown>;
18
+ req: PayloadRequest;
19
+ }) => string | Promise<string>;
20
+ /** Override the `admin.group` sidebar label. @default 'Sets' */
21
+ group?: string;
22
+ /** Additional collection hooks. */
23
+ hooks?: CollectionConfig['hooks'];
24
+ /** Extra set-level fields appended to the Settings tab, below the title. */
25
+ fields?: Field[];
26
+ /** Extra fields appended to each `iconsArray` row, after the built-in `name` + `icon`. */
27
+ iconRowFields?: Field[];
28
+ /** Enable drafts/versions on the collection (per-set draft → publish). @default true */
29
+ drafts?: boolean;
30
+ }
31
+ /**
32
+ * Builds the `iconSet` collection — a named `name → icon` mapping with a
33
+ * single-active toggle; the frontend renders the active set.
34
+ *
35
+ * `iconSlug` and `usagePanel` are wired by the plugin (from the icon collection's
36
+ * slug and the top-level `usagePanel` option); they aren't part of the public
37
+ * override surface.
38
+ */
39
+ export declare const createIconSetCollection: (opts?: IconSetCollectionOverrides & {
40
+ iconSlug?: string;
41
+ usagePanel?: boolean;
42
+ }) => CollectionConfig;
43
+ //# sourceMappingURL=IconSet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconSet.d.ts","sourceRoot":"","sources":["../../src/collections/IconSet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAOtF,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAAY,CAAA;AAWtC;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,GAAG,EAAE,cAAc,CAAA;KAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3G,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mCAAmC;IACnC,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAChB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,KAAK,EAAE,CAAA;IACvB,wFAAwF;IACxF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAM,0BAA0B,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,KAClF,gBAiFF,CAAA"}
@@ -0,0 +1,168 @@
1
+ import { activeField, enforceSingleActive } from "../lib/activeField.js";
2
+ import { authd } from "../lib/authenticated.js";
3
+ import { mergeHooks } from "../lib/mergeHooks.js";
4
+ import { toKebabCase } from "../lib/titleCase.js";
5
+ /** The default slug for the icon-set collection. */ export const ICON_SET_SLUG = 'iconSet';
6
+ /** Admin import-map path for the IconSet "requested icons" usage panel. */ const IconUsagePanelPath = '@pro-laico/payload-icons/admin/iconUsagePanel';
7
+ /** Admin import-map path for the per-row label inside `iconsArray`. */ const IconRowLabelPath = '@pro-laico/payload-icons/admin/iconRowLabel';
8
+ /** Inline title field so the collection has no template dependency. */ const titleField = (defaultValue = 'New Icon Set')=>({
9
+ name: 'title',
10
+ type: 'text',
11
+ required: true,
12
+ unique: true,
13
+ defaultValue
14
+ });
15
+ /**
16
+ * Builds the `iconSet` collection — a named `name → icon` mapping with a
17
+ * single-active toggle; the frontend renders the active set.
18
+ *
19
+ * `iconSlug` and `usagePanel` are wired by the plugin (from the icon collection's
20
+ * slug and the top-level `usagePanel` option); they aren't part of the public
21
+ * override surface.
22
+ */ export const createIconSetCollection = (opts = {})=>{
23
+ const { slug = ICON_SET_SLUG, iconSlug = 'icon', livePreviewUrl, group = 'Sets', hooks, fields: extraFields = [], iconRowFields = [], usagePanel = true, drafts = true } = opts;
24
+ // The "Requested icons" panel — a server UI field that scans usage (live in dev,
25
+ // manifest in prod) and diffs it against this set's icons. Config-free: the panel
26
+ // uses its defaults (ICON_USAGE_MANIFEST env for the manifest path in prod).
27
+ const usageField = usagePanel ? [
28
+ {
29
+ name: 'iconUsage',
30
+ type: 'ui',
31
+ admin: {
32
+ components: {
33
+ Field: IconUsagePanelPath
34
+ }
35
+ }
36
+ }
37
+ ] : [];
38
+ return {
39
+ slug,
40
+ labels: {
41
+ singular: 'Icon Set',
42
+ plural: 'Icon Sets'
43
+ },
44
+ access: {
45
+ create: authd,
46
+ delete: authd,
47
+ read: authd,
48
+ update: authd
49
+ },
50
+ admin: {
51
+ group,
52
+ useAsTitle: 'title',
53
+ defaultColumns: [
54
+ 'title',
55
+ 'active',
56
+ '_status'
57
+ ],
58
+ ...livePreviewUrl && {
59
+ preview: (data, { req })=>livePreviewUrl({
60
+ data: data,
61
+ req
62
+ }),
63
+ livePreview: {
64
+ url: ({ data, req })=>livePreviewUrl({
65
+ data: data,
66
+ req
67
+ })
68
+ }
69
+ }
70
+ },
71
+ fields: [
72
+ {
73
+ type: 'tabs',
74
+ tabs: [
75
+ {
76
+ label: 'Settings',
77
+ fields: [
78
+ {
79
+ type: 'row',
80
+ fields: [
81
+ activeField,
82
+ titleField('New Icon Set')
83
+ ]
84
+ },
85
+ ...extraFields,
86
+ ...usageField
87
+ ]
88
+ },
89
+ {
90
+ label: 'Icons',
91
+ fields: [
92
+ {
93
+ name: 'iconsArray',
94
+ type: 'array',
95
+ label: 'Icons',
96
+ labels: {
97
+ singular: 'Icon',
98
+ plural: 'Icons'
99
+ },
100
+ admin: {
101
+ initCollapsed: true,
102
+ components: {
103
+ RowLabel: IconRowLabelPath
104
+ }
105
+ },
106
+ fields: [
107
+ {
108
+ type: 'row',
109
+ fields: [
110
+ {
111
+ name: 'name',
112
+ type: 'text',
113
+ required: true,
114
+ // Normalize to kebab-case so the typed name matches `<Icon name>`.
115
+ hooks: {
116
+ beforeValidate: [
117
+ ({ value })=>typeof value === 'string' ? toKebabCase(value) : value
118
+ ]
119
+ },
120
+ admin: {
121
+ width: '25%',
122
+ description: 'The name the frontend looks the icon up by (auto kebab-cased).',
123
+ style: {
124
+ maxWidth: '350px'
125
+ }
126
+ }
127
+ },
128
+ {
129
+ name: 'icon',
130
+ type: 'upload',
131
+ relationTo: iconSlug,
132
+ displayPreview: false,
133
+ admin: {
134
+ allowCreate: false,
135
+ width: '75%',
136
+ description: 'Select an icon.',
137
+ style: {
138
+ maxWidth: '350px'
139
+ }
140
+ }
141
+ },
142
+ ...iconRowFields
143
+ ]
144
+ }
145
+ ]
146
+ }
147
+ ]
148
+ }
149
+ ]
150
+ }
151
+ ],
152
+ hooks: mergeHooks({
153
+ beforeChange: [
154
+ enforceSingleActive
155
+ ]
156
+ }, hooks),
157
+ ...drafts ? {
158
+ versions: {
159
+ drafts: {
160
+ schedulePublish: true
161
+ },
162
+ maxPerDoc: 50
163
+ }
164
+ } : {}
165
+ };
166
+ };
167
+
168
+ //# sourceMappingURL=IconSet.js.map