@driftime/sanity-plugin-icon 0.1.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/LICENSE +21 -0
- package/README.md +206 -0
- package/dist/drawing-Bo0HTICv.js +120 -0
- package/dist/drawing-Bo0HTICv.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +685 -0
- package/dist/index.js.map +1 -0
- package/dist/render.d.ts +21 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +19 -0
- package/dist/render.js.map +1 -0
- package/dist/types-nQUVfUHK.d.ts +16 -0
- package/dist/types-nQUVfUHK.d.ts.map +1 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Driftime®
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-dark.svg" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-light.svg" />
|
|
5
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-light.svg" alt="Icon plugin logo" width="48" />
|
|
6
|
+
</picture>
|
|
7
|
+
<h1>Icon</h1>
|
|
8
|
+
<p><strong>A Sanity Studio plugin by Driftime®</strong></p>
|
|
9
|
+
<p>Lucide icons for Sanity Studio, stored as drawings and rendered without the library.</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<br />
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Icon adds a field type to Sanity Studio that opens the whole Lucide library as a searchable grid. Authors find an icon by its name or by what it depicts, and the drawing itself is stored on the document alongside the name it was chosen by.
|
|
17
|
+
|
|
18
|
+
Rendering costs one component and **under 2 kB gzipped**, with no icon library in your application. That works because the document holds the drawing rather than a reference to it, so nothing needs resolving by name at runtime.
|
|
19
|
+
|
|
20
|
+
<br />
|
|
21
|
+
|
|
22
|
+
<figure>
|
|
23
|
+
<picture>
|
|
24
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-selector-dark.png" />
|
|
25
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-selector-light.png" />
|
|
26
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-selector-light.png" alt="The icon selector open in Sanity Studio, showing a search field above a scrolling grid of the complete Lucide library alongside a count of every icon it offers" />
|
|
27
|
+
</picture>
|
|
28
|
+
<p align="center"><sub><em>Every Lucide icon, searchable from inside the Studio.</em></sub></p>
|
|
29
|
+
</figure>
|
|
30
|
+
|
|
31
|
+
<br />
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Icon is built for Sanity Studio 6 and React 19, and declares both as peer dependencies, so your Studio must already be on those versions. You'll need Node 22.12 or later.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bun add -E @driftime/sanity-plugin-icon
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
<br />
|
|
42
|
+
|
|
43
|
+
## Basic Setup
|
|
44
|
+
|
|
45
|
+
Add the plugin to your Sanity configuration. It takes no options.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { defineConfig } from "sanity";
|
|
49
|
+
import { iconPlugin } from "@driftime/sanity-plugin-icon";
|
|
50
|
+
// ...
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
// ...
|
|
54
|
+
plugins: [iconPlugin()],
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
That registers an `icon` object type, available alongside Sanity's own. Use it on any document or object.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
defineField({
|
|
62
|
+
name: "icon",
|
|
63
|
+
type: "icon",
|
|
64
|
+
description: "Shown beside the heading.",
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The type is registered as `icon` and the name is not configurable, so a Studio that already defines its own `icon` type will need to rename that one.
|
|
69
|
+
|
|
70
|
+
<br />
|
|
71
|
+
|
|
72
|
+
## Picking an Icon
|
|
73
|
+
|
|
74
|
+
- Search matches an icon's name and the terms it is tagged with, so "next" finds `arrow-right`.
|
|
75
|
+
- The complete Lucide set is offered, and the count reflects whatever the current release holds.
|
|
76
|
+
- The last eight icons chosen are remembered, per browser rather than per dataset.
|
|
77
|
+
|
|
78
|
+
<br />
|
|
79
|
+
|
|
80
|
+
<figure>
|
|
81
|
+
<picture>
|
|
82
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-search-dark.png" />
|
|
83
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-search-light.png" />
|
|
84
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugin-icon/HEAD/.github/assets/icon-search-light.png" alt="The icon selector filtered by the term next, showing thirteen arrow and forward icons matched on what they depict rather than on their names" />
|
|
85
|
+
</picture>
|
|
86
|
+
<p align="center"><sub><em>Searching matches what an icon depicts, so “next” finds every arrow.</em></sub></p>
|
|
87
|
+
</figure>
|
|
88
|
+
|
|
89
|
+
<br />
|
|
90
|
+
|
|
91
|
+
## Rendering Icons
|
|
92
|
+
|
|
93
|
+
`Icon` is exported from a separate import path that holds no Studio code, so it can be used anywhere outside the Studio, in server and client components alike. It needs no icon library of its own.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { Icon } from "@driftime/sanity-plugin-icon/render";
|
|
97
|
+
|
|
98
|
+
<Icon value={home.icon} className="size-8" />;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It takes the stored field value directly and renders nothing when that value is absent or unreadable. Props extend `ComponentProps<"svg">`, so `className`, `style`, `aria-label` and the rest pass through to the element.
|
|
102
|
+
|
|
103
|
+
Type the field with `SanityIcon`, available from either import path.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import type { SanityIcon } from "@driftime/sanity-plugin-icon";
|
|
107
|
+
|
|
108
|
+
export interface SanityHome {
|
|
109
|
+
title: string;
|
|
110
|
+
icon?: SanityIcon;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
<br />
|
|
115
|
+
|
|
116
|
+
## Document Previews
|
|
117
|
+
|
|
118
|
+
A document's preview shows the icon its _type_ defines, which is the same glyph for every document of that type. `createIconPreview` builds preview media from the icon an author chose instead.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { createIconPreview } from "@driftime/sanity-plugin-icon";
|
|
122
|
+
|
|
123
|
+
preview: {
|
|
124
|
+
select: { title: "title", icon: "icon" },
|
|
125
|
+
prepare({ title, icon }) {
|
|
126
|
+
return { title, media: createIconPreview(icon) };
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It returns `undefined` when there is nothing to draw, so the preview falls back to Sanity's own placeholder.
|
|
132
|
+
|
|
133
|
+
<br />
|
|
134
|
+
|
|
135
|
+
## Querying
|
|
136
|
+
|
|
137
|
+
A stored icon needs no special handling in a query. It comes down as a plain object alongside the rest of the document, with nothing to dereference or expand.
|
|
138
|
+
|
|
139
|
+
```groq
|
|
140
|
+
*[_type == "home"][0] { title, icon }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<br />
|
|
144
|
+
|
|
145
|
+
## What Gets Stored
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"_type": "icon",
|
|
150
|
+
"name": "house",
|
|
151
|
+
"node": "[[\"path\",{\"d\":\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"}], …]"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The `name` records which icon was chosen, and the `node` holds the shapes it is drawn from. Both are written when the icon is picked, which is what lets it render without an icon library.
|
|
156
|
+
|
|
157
|
+
<br />
|
|
158
|
+
|
|
159
|
+
## Keeping the Icon Set Current
|
|
160
|
+
|
|
161
|
+
The plugin tracks Lucide within its major version, so new icons arrive without waiting for a plugin release. Your lockfile pins whichever version resolved at install, and removing and re-adding the plugin resolves it afresh.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
bun remove @driftime/sanity-plugin-icon
|
|
165
|
+
bun add -E @driftime/sanity-plugin-icon
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`bun update lucide-static` also works, and adds the icon library to your own dependencies so you control the version from then on.
|
|
169
|
+
|
|
170
|
+
<br />
|
|
171
|
+
|
|
172
|
+
## API
|
|
173
|
+
|
|
174
|
+
| Export | Import from | Purpose |
|
|
175
|
+
| -------------------------- | ------------------------------------- | ------------------------------------------ |
|
|
176
|
+
| `iconPlugin()` | `@driftime/sanity-plugin-icon` | Registers the `icon` type with the Studio. |
|
|
177
|
+
| `createIconPreview(value)` | `@driftime/sanity-plugin-icon` | Builds preview media from a stored icon. |
|
|
178
|
+
| `Icon` | `@driftime/sanity-plugin-icon/render` | Draws a stored icon. |
|
|
179
|
+
| `SanityIcon` | either | Type of the stored value. |
|
|
180
|
+
|
|
181
|
+
<br />
|
|
182
|
+
|
|
183
|
+
## Acknowledgements
|
|
184
|
+
|
|
185
|
+
Icons come from [Lucide](https://lucide.dev), distributed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE). Icons for controls the Studio already draws come from `@sanity/icons` so they match their surroundings.
|
|
186
|
+
|
|
187
|
+
<br />
|
|
188
|
+
<br />
|
|
189
|
+
|
|
190
|
+
<div align="center">
|
|
191
|
+
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
192
|
+
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
193
|
+
<p><a href="https://github.com/driftime/sanity-plugin-handbook">Handbook</a> · <a href="https://github.com/driftime/sanity-plugin-icon">Icon</a></p>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<br />
|
|
197
|
+
|
|
198
|
+
<div align="center">
|
|
199
|
+
<a href="https://driftime.com">
|
|
200
|
+
<picture>
|
|
201
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://driftime.com/driftime-github-logo-dark.svg" />
|
|
202
|
+
<source media="(prefers-color-scheme: light)" srcset="https://driftime.com/driftime-github-logo.svg" />
|
|
203
|
+
<img src="https://driftime.com/driftime-github-logo.svg" alt="Driftime® Logo" width="100" />
|
|
204
|
+
</picture>
|
|
205
|
+
</a>
|
|
206
|
+
</div>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import { stegaClean } from "@sanity/client/stega";
|
|
4
|
+
/**
|
|
5
|
+
* Checks whether a value is defined and non-empty, treating `false`, empty strings, and objects with
|
|
6
|
+
* no keys as absent. An array is absent unless one of its elements is itself present, while anything
|
|
7
|
+
* built from a class counts as present on existence alone. Not intended for boolean flags.
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to check.
|
|
10
|
+
* @returns True if the value is defined and not empty.
|
|
11
|
+
*/
|
|
12
|
+
function isDefined(value) {
|
|
13
|
+
if (value == null || value === !1) return !1;
|
|
14
|
+
if (typeof value == "string") return value.trim() !== "";
|
|
15
|
+
if (Array.isArray(value)) return value.some((element) => isDefined(element));
|
|
16
|
+
if (typeof value == "object") {
|
|
17
|
+
let prototype = Reflect.getPrototypeOf(value);
|
|
18
|
+
return prototype !== Object.prototype && prototype !== null || Object.keys(value).length > 0;
|
|
19
|
+
}
|
|
20
|
+
return !0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts a string between different casing formats.
|
|
24
|
+
*
|
|
25
|
+
* @param value - The string to convert.
|
|
26
|
+
* @param format - The target casing format.
|
|
27
|
+
* @returns The converted string.
|
|
28
|
+
*/
|
|
29
|
+
function convertCase(value, format) {
|
|
30
|
+
let words = value.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower> $<upper>").replaceAll(/[-_\s]+/gu, " ").trim().toLowerCase();
|
|
31
|
+
switch (format) {
|
|
32
|
+
case "kebab": return words.replaceAll(/\s+/gu, "-");
|
|
33
|
+
case "snake": return words.replaceAll(/\s+/gu, "_");
|
|
34
|
+
case "camel": return words.replaceAll(/\s+(?<character>.)/gu, (_match, character) => character.toUpperCase());
|
|
35
|
+
case "pascal": return words.replaceAll(/(?:^|\s+)(?<character>.)/gu, (_match, character) => character.toUpperCase());
|
|
36
|
+
case "title": return words.replaceAll(/\b\w/gu, (character) => character.toUpperCase());
|
|
37
|
+
case "sentence": return words.replaceAll(/^(?<character>.)/gu, (character) => character.toUpperCase());
|
|
38
|
+
default: return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Elements an icon's drawing may be built from, narrowed to those that describe a shape. */
|
|
42
|
+
const iconElements = [
|
|
43
|
+
"circle",
|
|
44
|
+
"ellipse",
|
|
45
|
+
"g",
|
|
46
|
+
"line",
|
|
47
|
+
"path",
|
|
48
|
+
"polygon",
|
|
49
|
+
"polyline",
|
|
50
|
+
"rect"
|
|
51
|
+
], iconRootAttributes = {
|
|
52
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
53
|
+
width: 24,
|
|
54
|
+
height: 24,
|
|
55
|
+
viewBox: "0 0 24 24",
|
|
56
|
+
fill: "none",
|
|
57
|
+
stroke: "currentColor",
|
|
58
|
+
strokeWidth: 2,
|
|
59
|
+
strokeLinecap: "round",
|
|
60
|
+
strokeLinejoin: "round"
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Checks whether a parsed value is an icon's drawing, since it arrives as whatever happened to be
|
|
64
|
+
* stored. Shapes are matched against the known elements so nothing else reaches the renderer.
|
|
65
|
+
*
|
|
66
|
+
* @param value - The value to check.
|
|
67
|
+
* @returns True if the value is a drawing.
|
|
68
|
+
*/
|
|
69
|
+
function isIconNode(value) {
|
|
70
|
+
return Array.isArray(value) ? value.every((element) => Array.isArray(element) && element.length === 2 && iconElements.some((name) => name === element[0]) && typeof element[1] == "object" && element[1] !== null) : !1;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Reads an icon's drawing from an already-parsed value, for a library handing over its drawings as
|
|
74
|
+
* data rather than as a document field.
|
|
75
|
+
*
|
|
76
|
+
* @param value - The value to read.
|
|
77
|
+
* @returns The drawing, or undefined when the value is not one.
|
|
78
|
+
*/
|
|
79
|
+
function parseIconNode(value) {
|
|
80
|
+
return isIconNode(value) ? value : void 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reads an icon's drawing back from the form it is stored in, clearing the stega characters a Sanity
|
|
84
|
+
* fetch leaves behind.
|
|
85
|
+
*
|
|
86
|
+
* @param value - The stored drawing.
|
|
87
|
+
* @returns The drawing, or undefined when nothing readable was stored.
|
|
88
|
+
*/
|
|
89
|
+
function resolveIconNode(value) {
|
|
90
|
+
let cleaned = stegaClean(value);
|
|
91
|
+
if (isDefined(cleaned)) try {
|
|
92
|
+
return parseIconNode(JSON.parse(cleaned));
|
|
93
|
+
} catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Writes an icon's drawing into the form it is stored in, so the shapes survive a round trip through
|
|
99
|
+
* a document rather than a component reference that only resolves at build time.
|
|
100
|
+
*
|
|
101
|
+
* @param node - The drawing to store.
|
|
102
|
+
* @returns The drawing ready to store.
|
|
103
|
+
*/
|
|
104
|
+
function serialiseIconNode(node) {
|
|
105
|
+
return JSON.stringify(node);
|
|
106
|
+
}
|
|
107
|
+
function Drawing({ node, ...props }) {
|
|
108
|
+
return isDefined(node) ? /* @__PURE__ */ jsx("svg", {
|
|
109
|
+
...iconRootAttributes,
|
|
110
|
+
"aria-hidden": "true",
|
|
111
|
+
...props,
|
|
112
|
+
children: node.map(([element, attributes], index) => createElement(element, {
|
|
113
|
+
...attributes,
|
|
114
|
+
key: String(index)
|
|
115
|
+
}))
|
|
116
|
+
}) : null;
|
|
117
|
+
}
|
|
118
|
+
export { convertCase as a, serialiseIconNode as i, parseIconNode as n, isDefined as o, resolveIconNode as r, Drawing as t };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=drawing-Bo0HTICv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawing-Bo0HTICv.js","names":["Nullable","isDefined","value","T","undefined","trim","Array","isArray","some","element","prototype","Reflect","getPrototypeOf","Object","keys","length","convertCase","format","words","replaceAll","toLowerCase","_match","character","toUpperCase","stegaClean","isDefined","iconElements","const","IconElement","element","attributes","Record","IconNode","iconRootAttributes","xmlns","width","height","viewBox","fill","stroke","strokeWidth","strokeLinecap","strokeLinejoin","isIconNode","value","Array","isArray","every","length","some","name","parseIconNode","undefined","resolveIconNode","cleaned","parsed","JSON","parse","serialiseIconNode","node","stringify","ComponentProps","createElement","IconNode","iconRootAttributes","isDefined","DrawingProps","Omit","node","Drawing","props","map","element","attributes","index","key","String"],"sources":["../src/lib/utils.ts","../src/lib/nodes.ts","../src/components/drawing.tsx"],"sourcesContent":["import type { Nullable } from \"@/types\";\n\n/**\n * Checks whether a value is defined and non-empty, treating `false`, empty strings, and objects with\n * no keys as absent. An array is absent unless one of its elements is itself present, while anything\n * built from a class counts as present on existence alone. Not intended for boolean flags.\n *\n * @param value - The value to check.\n * @returns True if the value is defined and not empty.\n */\nexport function isDefined<T>(value: Nullable<T> | false): value is T {\n if (value === undefined || value === null || value === false) return false;\n if (typeof value === \"string\") return value.trim() !== \"\";\n if (Array.isArray(value)) return value.some((element) => isDefined(element));\n\n if (typeof value === \"object\") {\n const prototype = Reflect.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return true;\n\n return Object.keys(value).length > 0;\n }\n\n return true;\n}\n\n/**\n * Converts a string between different casing formats.\n *\n * @param value - The string to convert.\n * @param format - The target casing format.\n * @returns The converted string.\n */\nexport function convertCase(value: string, format: \"kebab\" | \"snake\" | \"camel\" | \"pascal\" | \"title\" | \"sentence\") {\n const words = value\n .replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, \"$<lower> $<upper>\")\n .replaceAll(/[-_\\s]+/gu, \" \")\n .trim()\n .toLowerCase();\n\n switch (format) {\n case \"kebab\": {\n return words.replaceAll(/\\s+/gu, \"-\");\n }\n case \"snake\": {\n return words.replaceAll(/\\s+/gu, \"_\");\n }\n case \"camel\": {\n return words.replaceAll(/\\s+(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"pascal\": {\n return words.replaceAll(/(?:^|\\s+)(?<character>.)/gu, (_match, character: string) => character.toUpperCase());\n }\n case \"title\": {\n return words.replaceAll(/\\b\\w/gu, (character) => character.toUpperCase());\n }\n case \"sentence\": {\n return words.replaceAll(/^(?<character>.)/gu, (character) => character.toUpperCase());\n }\n default: {\n return value;\n }\n }\n}\n","import { stegaClean } from \"@sanity/client/stega\";\n\nimport { isDefined } from \"@/lib/utils\";\n\n/** Elements an icon's drawing may be built from, narrowed to those that describe a shape. */\nconst iconElements = [\"circle\", \"ellipse\", \"g\", \"line\", \"path\", \"polygon\", \"polyline\", \"rect\"] as const;\n\n/** Single shape in an icon's drawing, paired with the attributes it is drawn with. */\ntype IconElement = [element: (typeof iconElements)[number], attributes: Record<string, string>];\n\n/** An icon's drawing, held as the shapes it is built from rather than as a component. */\nexport type IconNode = IconElement[];\n\n/**\n * Attributes given to the SVG an icon's shapes are drawn inside, mirroring the grid and stroke Lucide\n * draws on. A library working to different conventions would need its own frame, which is why a\n * stored icon carries only what sits inside this one.\n */\nexport const iconRootAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n} as const;\n\n/**\n * Checks whether a parsed value is an icon's drawing, since it arrives as whatever happened to be\n * stored. Shapes are matched against the known elements so nothing else reaches the renderer.\n *\n * @param value - The value to check.\n * @returns True if the value is a drawing.\n */\nfunction isIconNode(value: unknown): value is IconNode {\n if (!Array.isArray(value)) return false;\n\n return value.every(\n (element: unknown) =>\n Array.isArray(element) &&\n element.length === 2 &&\n iconElements.some((name) => name === element[0]) &&\n typeof element[1] === \"object\" &&\n element[1] !== null,\n );\n}\n\n/**\n * Reads an icon's drawing from an already-parsed value, for a library handing over its drawings as\n * data rather than as a document field.\n *\n * @param value - The value to read.\n * @returns The drawing, or undefined when the value is not one.\n */\nexport function parseIconNode(value: unknown) {\n return isIconNode(value) ? value : undefined;\n}\n\n/**\n * Reads an icon's drawing back from the form it is stored in, clearing the stega characters a Sanity\n * fetch leaves behind.\n *\n * @param value - The stored drawing.\n * @returns The drawing, or undefined when nothing readable was stored.\n */\nexport function resolveIconNode(value: string | undefined) {\n const cleaned = stegaClean(value);\n if (!isDefined(cleaned)) return undefined;\n\n try {\n const parsed: unknown = JSON.parse(cleaned);\n\n return parseIconNode(parsed);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Writes an icon's drawing into the form it is stored in, so the shapes survive a round trip through\n * a document rather than a component reference that only resolves at build time.\n *\n * @param node - The drawing to store.\n * @returns The drawing ready to store.\n */\nexport function serialiseIconNode(node: IconNode) {\n return JSON.stringify(node);\n}\n","// Runs inside a consumer's server components, where the compiler's memo cache hook cannot.\n\"use no memo\";\n\nimport type { ComponentProps } from \"react\";\nimport { createElement } from \"react\";\n\nimport type { IconNode } from \"@/lib/nodes\";\nimport { iconRootAttributes } from \"@/lib/nodes\";\nimport { isDefined } from \"@/lib/utils\";\n\nexport type DrawingProps = Omit<ComponentProps<\"svg\">, \"children\"> & { node: IconNode | undefined };\n\nexport function Drawing({ node, ...props }: DrawingProps) {\n if (!isDefined(node)) return null;\n\n return (\n <svg {...iconRootAttributes} aria-hidden=\"true\" {...props}>\n {node.map(([element, attributes], index) => createElement(element, { ...attributes, key: String(index) }))}\n </svg>\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgBC,UAAaC,OAAwC;CACnE,IAAIA,SAAiC,QAAQA,UAAU,IAAO,OAAO;CACrE,IAAI,OAAOA,SAAU,UAAU,OAAOA,MAAMG,KAAK,MAAM;CACvD,IAAIC,MAAMC,QAAQL,KAAK,GAAG,OAAOA,MAAMM,MAAMC,YAAYR,UAAUQ,OAAO,CAAC;CAE3E,IAAI,OAAOP,SAAU,UAAU;EAC7B,IAAMQ,YAAYC,QAAQC,eAAeV,KAAK;EAG9C,OAFIQ,cAAcG,OAAOH,aAAaA,cAAc,QAE7CG,OAAOC,KAAKZ,KAAK,CAAC,CAACa,SAAS;CACrC;CAEA,OAAO;AACT;;;;;;;;AASA,SAAgBC,YAAYd,OAAee,QAAuE;CAChH,IAAMC,QAAQhB,MACXiB,WAAW,uCAAuC,mBAAmB,CAAC,CACtEA,WAAW,aAAa,GAAG,CAAC,CAC5Bd,KAAK,CAAC,CACNe,YAAY;CAEf,QAAQH,QAAR;EACE,KAAK,SACH,OAAOC,MAAMC,WAAW,SAAS,GAAG;EAEtC,KAAK,SACH,OAAOD,MAAMC,WAAW,SAAS,GAAG;EAEtC,KAAK,SACH,OAAOD,MAAMC,WAAW,yBAAyBE,QAAQC,cAAsBA,UAAUC,YAAY,CAAC;EAExG,KAAK,UACH,OAAOL,MAAMC,WAAW,+BAA+BE,QAAQC,cAAsBA,UAAUC,YAAY,CAAC;EAE9G,KAAK,SACH,OAAOL,MAAMC,WAAW,WAAWG,cAAcA,UAAUC,YAAY,CAAC;EAE1E,KAAK,YACH,OAAOL,MAAMC,WAAW,uBAAuBG,cAAcA,UAAUC,YAAY,CAAC;EAEtF,SACE,OAAOrB;CAEX;AACF;;ACzDA,MAAMwB,eAAe;CAAC;CAAU;CAAW;CAAK;CAAQ;CAAQ;CAAW;CAAY;AAAM,GAahFO,qBAAqB;CAChCC,OAAO;CACPC,OAAO;CACPC,QAAQ;CACRC,SAAS;CACTC,MAAM;CACNC,QAAQ;CACRC,aAAa;CACbC,eAAe;CACfC,gBAAgB;AAClB;;;;;;;;AASA,SAASC,WAAWC,OAAmC;CAGrD,OAFKC,MAAMC,QAAQF,KAAK,IAEjBA,MAAMG,OACVlB,YACCgB,MAAMC,QAAQjB,OAAO,KACrBA,QAAQmB,WAAW,KACnBtB,aAAauB,MAAMC,SAASA,SAASrB,QAAQ,EAAE,KAC/C,OAAOA,QAAQ,MAAO,YACtBA,QAAQ,OAAO,IACnB,IATkC;AAUpC;;;;;;;;AASA,SAAgBsB,cAAcP,OAAgB;CAC5C,OAAOD,WAAWC,KAAK,IAAIA,QAAQQ,KAAAA;AACrC;;;;;;;;AASA,SAAgBC,gBAAgBT,OAA2B;CACzD,IAAMU,UAAU9B,WAAWoB,KAAK;CAC3BnB,cAAU6B,OAAO,GAEtB,IAAI;EAGF,OAAOH,cAFiBK,KAAKC,MAAMH,OAEdC,CAAM;CAC7B,QAAQ;EACN;CACF;AACF;;;;;;;;AASA,SAAgBG,kBAAkBC,MAAgB;CAChD,OAAOH,KAAKI,UAAUD,IAAI;AAC5B;AC9EA,SAAgBU,QAAQ,EAAED,MAAM,GAAGE,SAAuB;CAGxD,OAFKL,UAAUG,IAAI,IAGjB,oBAAC,OAAD;EAAK,GAAIJ;EAAoB,eAAY;EAAO,GAAIM;EACjDF,UAAAA,KAAKG,KAAK,CAACC,SAASC,aAAaC,UAAUZ,cAAcU,SAAS;GAAE,GAAGC;GAAYE,KAAKC,OAAOF,KAAK;EAAE,CAAC,CAAC;CACtG,CAAA,IALsB;AAO/B"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { t as SanityIcon } from "./types-nQUVfUHK.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates the icon field type for Sanity Studio, offering the Lucide library through a searchable
|
|
4
|
+
* picker and storing the chosen drawing ready to render.
|
|
5
|
+
*
|
|
6
|
+
* @returns Sanity plugin definition.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare const iconPlugin: import("sanity").Plugin<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Builds the media a stored icon previews with, so a document lists the drawing an author chose
|
|
12
|
+
* rather than one placeholder standing in for every icon alike.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The stored icon.
|
|
15
|
+
* @returns The media component, or undefined when nothing readable was stored.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
declare function createIconPreview(value: SanityIcon | undefined): (() => import("react").JSX.Element) | undefined;
|
|
19
|
+
export { type SanityIcon, createIconPreview, iconPlugin };
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.ts","../src/components/preview.tsx"],"mappings":";;;;;;;;cAWa,6BAAU;;;;;;;;;iBCGP,kBAAkB,OAAO,gDAAsB,IAAA"}
|