@driftime/sanity-plugin-icon 0.1.0 → 0.3.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/README.md +179 -71
- package/dist/{drawing-Bo0HTICv.js → drawing-BThiR2bN.js} +14 -5
- package/dist/drawing-BThiR2bN.js.map +1 -0
- package/dist/index.d.ts +56 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +550 -342
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +5 -5
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +1 -2
- package/dist/render.js.map +1 -1
- package/dist/{types-nQUVfUHK.d.ts → types-_D2jGm25.d.ts} +2 -2
- package/dist/types-_D2jGm25.d.ts.map +1 -0
- package/package.json +21 -30
- package/LICENSE +0 -21
- package/dist/drawing-Bo0HTICv.js.map +0 -1
- package/dist/types-nQUVfUHK.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
4
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
5
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-dark.svg" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-light.svg" />
|
|
5
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-light.svg" alt="Icon plugin logo" width="48" />
|
|
6
6
|
</picture>
|
|
7
7
|
<h1>Icon</h1>
|
|
8
8
|
<p><strong>A Sanity Studio plugin by Driftime®</strong></p>
|
|
9
|
-
<p>Lucide icons for Sanity Studio, stored as
|
|
9
|
+
<p>Lucide icons for Sanity Studio, stored as SVG and rendered without the library.</p>
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@driftime/sanity-plugin-icon"><img src="https://img.shields.io/npm/v/@driftime/sanity-plugin-icon?style=flat-square&labelColor=1a1a1a&color=666666" alt="npm version" /></a>
|
|
12
|
+
<a href="https://github.com/driftime/sanity-plugins/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@driftime/sanity-plugin-icon?style=flat-square&labelColor=1a1a1a&color=666666" alt="License: MIT" /></a>
|
|
13
|
+
</p>
|
|
10
14
|
</div>
|
|
11
15
|
|
|
12
16
|
<br />
|
|
13
17
|
|
|
14
18
|
## Overview
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
Icons are part of a site's visual language, chosen with the same care as its typography and its colors, and Icon makes them part of its content too. The whole [Lucide](https://lucide.dev) library sits behind a field, searchable by name or by what an icon depicts, so the icon beside a heading, a feature, or a link is chosen where the heading is written.
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
When an icon is chosen, its drawing is stored on the document alongside its name. The site renders that drawing with one component and no icon library, so nothing is looked up at runtime, and an icon keeps rendering through every Lucide release, whatever that release renames or removes.
|
|
19
23
|
|
|
20
24
|
<br />
|
|
21
25
|
|
|
22
26
|
<figure>
|
|
23
27
|
<picture>
|
|
24
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
25
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
26
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
28
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-selector-dark.png" />
|
|
29
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-selector-light.png" />
|
|
30
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-selector-light.png" alt="The icon picker open in Sanity Studio, showing a search field above a scrolling grid of every Lucide icon" />
|
|
27
31
|
</picture>
|
|
28
|
-
<p align="center"><sub><em>
|
|
32
|
+
<p align="center"><sub><em>The icon picker, with the whole library in a searchable grid.</em></sub></p>
|
|
29
33
|
</figure>
|
|
30
34
|
|
|
31
35
|
<br />
|
|
32
36
|
|
|
33
37
|
## Installation
|
|
34
38
|
|
|
35
|
-
Icon is built for Sanity Studio 6 and React 19
|
|
39
|
+
Icon is built for Sanity Studio 6.10 and React 19 and declares both as peer dependencies, so the Studio needs to be on those versions already. Node 22.12 or later is required.
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
42
|
bun add -E @driftime/sanity-plugin-icon
|
|
@@ -40,101 +44,178 @@ bun add -E @driftime/sanity-plugin-icon
|
|
|
40
44
|
|
|
41
45
|
<br />
|
|
42
46
|
|
|
43
|
-
##
|
|
47
|
+
## Studio
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
In the Studio, the plugin registers an `icon` type and takes the icons every field offers.
|
|
50
|
+
|
|
51
|
+
<br />
|
|
52
|
+
|
|
53
|
+
### Registering the Plugin
|
|
54
|
+
|
|
55
|
+
Nothing is required, and a plugin registered on its own offers every icon in the installed Lucide release.
|
|
46
56
|
|
|
47
57
|
```typescript
|
|
48
58
|
import { defineConfig } from "sanity";
|
|
49
59
|
import { iconPlugin } from "@driftime/sanity-plugin-icon";
|
|
50
|
-
// ...
|
|
51
60
|
|
|
52
61
|
export default defineConfig({
|
|
53
62
|
// ...
|
|
54
|
-
plugins: [
|
|
63
|
+
plugins: [
|
|
64
|
+
// ...
|
|
65
|
+
iconPlugin(),
|
|
66
|
+
],
|
|
55
67
|
});
|
|
56
68
|
```
|
|
57
69
|
|
|
58
|
-
|
|
70
|
+
| Option | Type | Default | Purpose |
|
|
71
|
+
| ------- | ------------------ | ------- | --------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `icons` | `SanityIconName[]` | all | Icons every field offers, in the order listed. See [Restricting the Icons](#restricting-the-icons). |
|
|
73
|
+
|
|
74
|
+
<br />
|
|
75
|
+
|
|
76
|
+
### Adding an Icon Field
|
|
77
|
+
|
|
78
|
+
A field of type `icon` is defined like any other.
|
|
59
79
|
|
|
60
80
|
```typescript
|
|
61
81
|
defineField({
|
|
62
82
|
name: "icon",
|
|
63
83
|
type: "icon",
|
|
64
|
-
description: "
|
|
84
|
+
description: "Icon shown beside the heading.",
|
|
65
85
|
});
|
|
66
86
|
```
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
| Option | Type | Default | Purpose |
|
|
89
|
+
| ------- | ------------------ | ----------------- | ----------------------------------------------------------------------------- |
|
|
90
|
+
| `icons` | `SanityIconName[]` | the plugin's list | Icons this field offers. See [Restricting the Icons](#restricting-the-icons). |
|
|
91
|
+
|
|
92
|
+
The type name is always `icon`, so a Studio that already has a type by that name needs to rename it before installing.
|
|
69
93
|
|
|
70
94
|
<br />
|
|
71
95
|
|
|
72
|
-
|
|
96
|
+
### Restricting the Icons
|
|
97
|
+
|
|
98
|
+
A field offers the whole library unless restricted, and most sites want a smaller set. Pass the icons the site uses to the plugin, and every field offers those, in the order they are listed.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
iconPlugin({
|
|
102
|
+
icons: ["sun", "wind", "droplets", "leaf", "sprout", "mountain", "tent", "compass"],
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A single field sometimes needs a list of its own, for example a contact card whose icon should only ever be a mail, phone, or globe symbol. Pass `icons` in the field's `options`.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
defineField({
|
|
110
|
+
name: "icon",
|
|
111
|
+
type: "icon",
|
|
112
|
+
description: "Icon shown beside the contact method.",
|
|
113
|
+
options: { icons: ["mail", "phone", "globe"] },
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A field's list **replaces** the plugin's list, so a field can offer any icon regardless of the plugin's, and a name the installed Lucide release does not have is skipped. Restricting a field later is safe, because a stored icon carries its own drawing and still renders when the picker no longer offers it.
|
|
118
|
+
|
|
119
|
+
Names are typed against the Lucide release installed in the project, so they complete as they are typed. A list shared between the plugin and a field, or between several fields, is typed with `SanityIconName`.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import type { SanityIconName } from "@driftime/sanity-plugin-icon";
|
|
73
123
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- The last eight icons chosen are remembered, per browser rather than per dataset.
|
|
124
|
+
export const contactIcons: SanityIconName[] = ["mail", "phone", "globe"];
|
|
125
|
+
```
|
|
77
126
|
|
|
78
127
|
<br />
|
|
79
128
|
|
|
80
129
|
<figure>
|
|
81
130
|
<picture>
|
|
82
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
83
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
84
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
131
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-search-dark.png" />
|
|
132
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-search-light.png" />
|
|
133
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/icon/assets/icon-search-light.png" alt="The icon picker filtered by the search term next, showing every arrow and forward icon in the library" />
|
|
85
134
|
</picture>
|
|
86
|
-
<p align="center"><sub><em>
|
|
135
|
+
<p align="center"><sub><em>Search matches an icon's name and the terms Lucide tags it with, so "next" finds every arrow.</em></sub></p>
|
|
87
136
|
</figure>
|
|
88
137
|
|
|
89
138
|
<br />
|
|
90
139
|
|
|
91
|
-
|
|
140
|
+
### Document Previews
|
|
92
141
|
|
|
93
|
-
|
|
142
|
+
A document's preview shows the icon its type was given in the schema, which is the same for every document of that type. `createIconPreview` shows the icon the author chose instead. Pass it the stored value in `prepare`.
|
|
94
143
|
|
|
95
|
-
```
|
|
96
|
-
import {
|
|
144
|
+
```typescript
|
|
145
|
+
import { createIconPreview } from "@driftime/sanity-plugin-icon";
|
|
97
146
|
|
|
98
|
-
|
|
147
|
+
defineType({
|
|
148
|
+
// ...
|
|
149
|
+
preview: {
|
|
150
|
+
select: { title: "title", icon: "icon" },
|
|
151
|
+
prepare({ title, icon }) {
|
|
152
|
+
return { title, media: createIconPreview(icon) };
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
99
156
|
```
|
|
100
157
|
|
|
101
|
-
|
|
158
|
+
When nothing readable is stored, it returns `undefined` and the preview falls back to the type's own icon.
|
|
159
|
+
|
|
160
|
+
<br />
|
|
102
161
|
|
|
103
|
-
|
|
162
|
+
### Keeping the Library Current
|
|
104
163
|
|
|
105
|
-
|
|
106
|
-
import type { SanityIcon } from "@driftime/sanity-plugin-icon";
|
|
164
|
+
The plugin depends on Lucide with a caret range, so a newer release installs without a plugin release. The project's lockfile pins whichever release was installed, and there are two ways to move it forward. Removing and re-adding the plugin resolves the range again.
|
|
107
165
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
166
|
+
```bash
|
|
167
|
+
bun remove @driftime/sanity-plugin-icon
|
|
168
|
+
bun add -E @driftime/sanity-plugin-icon
|
|
112
169
|
```
|
|
113
170
|
|
|
171
|
+
Adding `lucide-static` to the project's own dependencies puts the release under the project's control, and the plugin resolves to that copy from then on.
|
|
172
|
+
|
|
114
173
|
<br />
|
|
115
174
|
|
|
116
|
-
##
|
|
175
|
+
## Site
|
|
117
176
|
|
|
118
|
-
|
|
177
|
+
On the site, one component draws a stored icon, and it is imported from `@driftime/sanity-plugin-icon/render`. That path carries no Studio code, so it is safe in server components and anywhere else on the site.
|
|
119
178
|
|
|
120
|
-
|
|
121
|
-
import { createIconPreview } from "@driftime/sanity-plugin-icon";
|
|
179
|
+
<br />
|
|
122
180
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
181
|
+
### Rendering Icons
|
|
182
|
+
|
|
183
|
+
`Icon` draws the shapes held in the stored value, so no icon library is installed on the site. The markup is an `svg` element, and the component renders nothing when the value is missing or unreadable.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { Icon } from "@driftime/sanity-plugin-icon/render";
|
|
187
|
+
import type { SanityIcon } from "@driftime/sanity-plugin-icon/render";
|
|
188
|
+
|
|
189
|
+
interface FeatureProps {
|
|
190
|
+
icon: SanityIcon | undefined;
|
|
191
|
+
title: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function Feature({ icon, title }: FeatureProps) {
|
|
195
|
+
return (
|
|
196
|
+
<article>
|
|
197
|
+
<Icon value={icon} className="size-8" />
|
|
198
|
+
<h3>{title}</h3>
|
|
199
|
+
</article>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
129
202
|
```
|
|
130
203
|
|
|
131
|
-
|
|
204
|
+
| Prop | Type | Purpose |
|
|
205
|
+
| ------- | ------------------------- | ------------------------------------------------------ |
|
|
206
|
+
| `value` | `SanityIcon \| undefined` | The stored icon. Required, though it may hold nothing. |
|
|
207
|
+
|
|
208
|
+
Every other prop is an `svg` element's and is spread onto the root element after the plugin's own attributes, so `className`, `style`, and `strokeWidth` all apply. The icon is drawn on Lucide's frame, 24 by 24 with a stroke of 2, and `aria-hidden`, so assistive technology treats it as decoration beside its text. An icon that carries meaning on its own takes `aria-hidden={false}` and an `aria-label`.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<Icon value={link.icon} aria-hidden={false} aria-label="Opens in a new tab" />
|
|
212
|
+
```
|
|
132
213
|
|
|
133
214
|
<br />
|
|
134
215
|
|
|
135
|
-
|
|
216
|
+
### Querying
|
|
136
217
|
|
|
137
|
-
|
|
218
|
+
An icon needs nothing special in a query. It comes back as a plain object with the rest of the document.
|
|
138
219
|
|
|
139
220
|
```groq
|
|
140
221
|
*[_type == "home"][0] { title, icon }
|
|
@@ -142,47 +223,74 @@ A stored icon needs no special handling in a query. It comes down as a plain obj
|
|
|
142
223
|
|
|
143
224
|
<br />
|
|
144
225
|
|
|
145
|
-
##
|
|
226
|
+
## Reference
|
|
227
|
+
|
|
228
|
+
The rest of this document covers the shape of a stored icon and everything the package exports.
|
|
229
|
+
|
|
230
|
+
<br />
|
|
231
|
+
|
|
232
|
+
### Stored Icons
|
|
233
|
+
|
|
234
|
+
`SanityIcon` is the type of a stored icon.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import type { SanityIcon } from "@driftime/sanity-plugin-icon/render";
|
|
238
|
+
|
|
239
|
+
export interface SanityFeature {
|
|
240
|
+
icon?: SanityIcon;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
146
243
|
|
|
147
244
|
```json
|
|
148
245
|
{
|
|
149
246
|
"_type": "icon",
|
|
150
247
|
"name": "house",
|
|
151
|
-
"node": "[[\"path\",{\"d\":\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"}],
|
|
248
|
+
"node": "[[\"path\",{\"d\":\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"}],[\"path\",{\"d\":\"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"}]]"
|
|
152
249
|
}
|
|
153
250
|
```
|
|
154
251
|
|
|
155
|
-
|
|
252
|
+
| Field | Type | Purpose |
|
|
253
|
+
| ------- | -------- | ------------------------------------------------------------------------------------- |
|
|
254
|
+
| `_type` | `string` | Always `icon`. |
|
|
255
|
+
| `name` | `string` | The icon's name in the Lucide release it was chosen from. |
|
|
256
|
+
| `node` | `string` | The shapes inside the icon's `svg` element, as JSON. Written when the icon is chosen. |
|
|
257
|
+
|
|
258
|
+
Both fields are written together, which is what lets an icon render without a library. A stored icon with a name but no drawing fails validation with a prompt to select it again, so a document cannot be published with an icon that renders as nothing.
|
|
156
259
|
|
|
157
260
|
<br />
|
|
158
261
|
|
|
159
|
-
|
|
262
|
+
### API
|
|
160
263
|
|
|
161
|
-
|
|
264
|
+
| Export | Import from | Purpose |
|
|
265
|
+
| -------------------------- | ------------------------------------- | ---------------------------------------- |
|
|
266
|
+
| `iconPlugin(config?)` | `@driftime/sanity-plugin-icon` | Registers the `icon` type. |
|
|
267
|
+
| `createIconPreview(value)` | `@driftime/sanity-plugin-icon` | Builds preview media from a stored icon. |
|
|
268
|
+
| `Icon` | `@driftime/sanity-plugin-icon/render` | Draws a stored icon. |
|
|
162
269
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```
|
|
270
|
+
<br />
|
|
271
|
+
|
|
272
|
+
### Types
|
|
167
273
|
|
|
168
|
-
|
|
274
|
+
| Type | Import from | Purpose |
|
|
275
|
+
| ---------------------- | ------------------------------------- | ---------------------------------------------------------------- |
|
|
276
|
+
| `SanityIconConfig` | `@driftime/sanity-plugin-icon` | Everything `iconPlugin` accepts. |
|
|
277
|
+
| `SanityIconOptions` | `@driftime/sanity-plugin-icon` | The `options` an icon field accepts. |
|
|
278
|
+
| `SanityIconDefinition` | `@driftime/sanity-plugin-icon` | A field or array member of type `icon`. |
|
|
279
|
+
| `SanityIconName` | `@driftime/sanity-plugin-icon` | The name of an icon in the installed Lucide release. |
|
|
280
|
+
| `SanityIconProps` | `@driftime/sanity-plugin-icon/render` | Everything `Icon` accepts: an `svg` element's props and `value`. |
|
|
281
|
+
| `SanityIcon` | `@driftime/sanity-plugin-icon/render` | A stored icon, with its name and its drawing. |
|
|
169
282
|
|
|
170
283
|
<br />
|
|
171
284
|
|
|
172
|
-
##
|
|
285
|
+
## License
|
|
173
286
|
|
|
174
|
-
|
|
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. |
|
|
287
|
+
MIT © [Driftime®](https://driftime.com). See [LICENSE](https://github.com/driftime/sanity-plugins/blob/main/LICENSE).
|
|
180
288
|
|
|
181
289
|
<br />
|
|
182
290
|
|
|
183
291
|
## Acknowledgements
|
|
184
292
|
|
|
185
|
-
Icons
|
|
293
|
+
Icons are from [Lucide](https://lucide.dev), distributed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE). Icons for the Studio's own controls come from `@sanity/icons`, so they match the rest of the Studio.
|
|
186
294
|
|
|
187
295
|
<br />
|
|
188
296
|
<br />
|
|
@@ -190,7 +298,7 @@ Icons come from [Lucide](https://lucide.dev), distributed under the [ISC License
|
|
|
190
298
|
<div align="center">
|
|
191
299
|
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
192
300
|
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
193
|
-
<p><a href="https://github.com/driftime/sanity-
|
|
301
|
+
<p><a href="https://github.com/driftime/sanity-plugins#readme">Sanity Plugins</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/handbook#readme">Handbook</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/icon#readme">Icon</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/color#readme">Color</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/link#readme">Link</a></p>
|
|
194
302
|
</div>
|
|
195
303
|
|
|
196
304
|
<br />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
1
|
import { createElement } from "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { stegaClean } from "@sanity/client/stega";
|
|
4
4
|
/**
|
|
5
5
|
* Checks whether a value is defined and non-empty, treating `false`, empty strings, and objects with
|
|
@@ -38,6 +38,15 @@ function convertCase(value, format) {
|
|
|
38
38
|
default: return value;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether a value can be read by key, narrowing what arrives untyped from outside the plugin.
|
|
43
|
+
*
|
|
44
|
+
* @param value - The value to check.
|
|
45
|
+
* @returns True if the value has keys to read.
|
|
46
|
+
*/
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value == "object" && !!value;
|
|
49
|
+
}
|
|
41
50
|
/** Elements an icon's drawing may be built from, narrowed to those that describe a shape. */
|
|
42
51
|
const iconElements = [
|
|
43
52
|
"circle",
|
|
@@ -67,7 +76,7 @@ const iconElements = [
|
|
|
67
76
|
* @returns True if the value is a drawing.
|
|
68
77
|
*/
|
|
69
78
|
function isIconNode(value) {
|
|
70
|
-
return Array.isArray(value) ? value.every((element) => Array.isArray(element) && element.length === 2 && iconElements.some((name) => name === element[0]) &&
|
|
79
|
+
return Array.isArray(value) ? value.every((element) => Array.isArray(element) && element.length === 2 && iconElements.some((name) => name === element[0]) && isRecord(element[1])) : !1;
|
|
71
80
|
}
|
|
72
81
|
/**
|
|
73
82
|
* Reads an icon's drawing from an already-parsed value, for a library handing over its drawings as
|
|
@@ -101,7 +110,7 @@ function resolveIconNode(value) {
|
|
|
101
110
|
* @param node - The drawing to store.
|
|
102
111
|
* @returns The drawing ready to store.
|
|
103
112
|
*/
|
|
104
|
-
function
|
|
113
|
+
function serializeIconNode(node) {
|
|
105
114
|
return JSON.stringify(node);
|
|
106
115
|
}
|
|
107
116
|
function Drawing({ node, ...props }) {
|
|
@@ -115,6 +124,6 @@ function Drawing({ node, ...props }) {
|
|
|
115
124
|
}))
|
|
116
125
|
}) : null;
|
|
117
126
|
}
|
|
118
|
-
export { convertCase as a,
|
|
127
|
+
export { convertCase as a, serializeIconNode as i, parseIconNode as n, isDefined as o, resolveIconNode as r, Drawing as t };
|
|
119
128
|
|
|
120
|
-
//# sourceMappingURL=drawing-
|
|
129
|
+
//# sourceMappingURL=drawing-BThiR2bN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawing-BThiR2bN.js","names":["Nullable","T","isDefined","value","undefined","trim","Array","isArray","some","element","prototype","Reflect","getPrototypeOf","Object","keys","length","convertCase","format","words","replaceAll","toLowerCase","_match","character","toUpperCase","isRecord","Record","readPath","path","current","segment","isDefined","isRecord","stegaClean","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","serializeIconNode","node","stringify","isDefined","ComponentProps","createElement","IconNode","iconRootAttributes","DrawingProps","Omit","node","Drawing","props","map","element","attributes","index","key","String"],"sources":["../../@repo/lib/utils.ts","../src/lib/nodes.ts","../src/components/drawing.tsx"],"sourcesContent":["/** A value that may be absent, covering both null and undefined. */\ntype Nullable<T> = T | null | undefined;\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\n/**\n * Checks whether a value can be read by key, narrowing what arrives untyped from outside the plugin.\n *\n * @param value - The value to check.\n * @returns True if the value has keys to read.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Reads a value out of nested records by following a path of keys, so a caller can name something\n * inside a shape it does not otherwise know.\n *\n * @param value - The object to read from.\n * @param path - Keys leading to the value.\n * @returns The value at the path, or undefined when a key is missing or a step holds no keys to read.\n */\nexport function readPath(value: unknown, path: string[]) {\n let current: unknown = value;\n\n for (const segment of path) {\n if (!isRecord(current)) return undefined;\n\n current = current[segment];\n }\n\n return current;\n}\n","import { isDefined, isRecord } from \"@repo/lib/utils\";\nimport { stegaClean } from \"@sanity/client/stega\";\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 isRecord(element[1]),\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 serializeIconNode(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 { isDefined } from \"@repo/lib/utils\";\nimport type { ComponentProps } from \"react\";\nimport { createElement } from \"react\";\n\nimport type { IconNode } from \"@/lib/nodes\";\nimport { iconRootAttributes } from \"@/lib/nodes\";\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":";;;;;;;;;;;AAWA,SAAgBE,UAAaC,OAAwC;CACnE,IAAIA,SAAiC,QAAQA,UAAU,IAAO,OAAO;CACrE,IAAI,OAAOA,SAAU,UAAU,OAAOA,MAAME,KAAK,MAAM;CACvD,IAAIC,MAAMC,QAAQJ,KAAK,GAAG,OAAOA,MAAMK,MAAMC,YAAYP,UAAUO,OAAO,CAAC;CAE3E,IAAI,OAAON,SAAU,UAAU;EAC7B,IAAMO,YAAYC,QAAQC,eAAeT,KAAK;EAG9C,OAFIO,cAAcG,OAAOH,aAAaA,cAAc,QAE7CG,OAAOC,KAAKX,KAAK,CAAC,CAACY,SAAS;CACrC;CAEA,OAAO;AACT;;;;;;;;AASA,SAAgBC,YAAYb,OAAec,QAAuE;CAChH,IAAMC,QAAQf,MACXgB,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,OAAOpB;CAEX;AACF;;;;;;;AAQA,SAAgBqB,SAASrB,OAAkD;CACzE,OAAO,OAAOA,SAAU,cAAYA;AACtC;;ACrEA,MAAM8B,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/CL,SAASK,QAAQ,EAAE,CACvB,IARkC;AASpC;;;;;;;;AASA,SAAgBsB,cAAcP,OAAgB;CAC5C,OAAOD,WAAWC,KAAK,IAAIA,QAAQQ,KAAAA;AACrC;;;;;;;;AASA,SAAgBC,gBAAgBT,OAA2B;CACzD,IAAMU,UAAU7B,WAAWmB,KAAK;CAC3BrB,cAAU+B,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;AC5EA,SAAgBU,QAAQ,EAAED,MAAM,GAAGE,SAAuB;CAGxD,OAFKT,UAAUO,IAAI,IAGjB,oBAAC,OAAD;EAAK,GAAIH;EAAoB,eAAY;EAAO,GAAIK;EACjDF,UAAAA,KAAKG,KAAK,CAACC,SAASC,aAAaC,UAAUX,cAAcS,SAAS;GAAE,GAAGC;GAAYE,KAAKC,OAAOF,KAAK;EAAE,CAAC,CAAC;CACtG,CAAA,IALsB;AAO/B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,64 @@
|
|
|
1
|
-
import { t as SanityIcon } from "./types-
|
|
1
|
+
import { n as iconTypeName, t as SanityIcon } from "./types-_D2jGm25.js";
|
|
2
|
+
import { ObjectOptions, TypeAliasDefinition } from "sanity";
|
|
3
|
+
import { ComponentProps } from "react";
|
|
4
|
+
import iconNodes from "lucide-static/icon-nodes.json";
|
|
5
|
+
declare module "@sanity/types" {
|
|
6
|
+
interface IntrinsicDefinitions {
|
|
7
|
+
icon: SanityIconDefinition;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Names of every icon the library offers, read from the Lucide release a project resolved rather than
|
|
12
|
+
* from a list this plugin ships, so an icon added since the last plugin release still completes.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
type SanityIconName = keyof typeof iconNodes;
|
|
17
|
+
/**
|
|
18
|
+
* Options an icon field takes, narrowing what its picker offers.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
interface SanityIconOptions extends ObjectOptions {
|
|
23
|
+
/** Icons an author may choose from, offered in the order they are named. Every icon when omitted. */
|
|
24
|
+
icons?: SanityIconName[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Shape of a field or array member holding an icon, so its options are completed and checked the way
|
|
28
|
+
* a built-in type's are.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
interface SanityIconDefinition extends Omit<TypeAliasDefinition<typeof iconTypeName, undefined>, "options"> {
|
|
33
|
+
options?: SanityIconOptions;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Everything the plugin accepts, all of which may be left out.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
interface SanityIconConfig {
|
|
41
|
+
/** Icons an author may choose from, offered in the order they are named. Every icon when omitted. */
|
|
42
|
+
icons?: SanityIconName[];
|
|
43
|
+
}
|
|
2
44
|
/**
|
|
3
45
|
* Creates the icon field type for Sanity Studio, offering the Lucide library through a searchable
|
|
4
46
|
* picker and storing the chosen drawing ready to render.
|
|
5
47
|
*
|
|
48
|
+
* @param config - Plugin configuration.
|
|
6
49
|
* @returns Sanity plugin definition.
|
|
7
50
|
* @public
|
|
8
51
|
*/
|
|
9
|
-
declare
|
|
52
|
+
export declare function iconPlugin(config?: SanityIconConfig): import("sanity").PluginOptions;
|
|
53
|
+
/** Elements an icon's drawing may be built from, narrowed to those that describe a shape. */
|
|
54
|
+
declare const iconElements: readonly ["circle", "ellipse", "g", "line", "path", "polygon", "polyline", "rect"];
|
|
55
|
+
/** Single shape in an icon's drawing, paired with the attributes it is drawn with. */
|
|
56
|
+
type IconElement = [element: (typeof iconElements)[number], attributes: Record<string, string>];
|
|
57
|
+
/** An icon's drawing, held as the shapes it is built from rather than as a component. */
|
|
58
|
+
type IconNode = IconElement[];
|
|
59
|
+
type DrawingProps = Omit<ComponentProps<"svg">, "children"> & {
|
|
60
|
+
node: IconNode | undefined;
|
|
61
|
+
};
|
|
10
62
|
/**
|
|
11
63
|
* Builds the media a stored icon previews with, so a document lists the drawing an author chose
|
|
12
64
|
* rather than one placeholder standing in for every icon alike.
|
|
@@ -15,6 +67,6 @@ declare const iconPlugin: import("sanity").Plugin<void>;
|
|
|
15
67
|
* @returns The media component, or undefined when nothing readable was stored.
|
|
16
68
|
* @public
|
|
17
69
|
*/
|
|
18
|
-
declare function createIconPreview(value: SanityIcon | undefined): (() => import("react").
|
|
19
|
-
export {
|
|
70
|
+
export declare function createIconPreview(value: SanityIcon | undefined): (() => import("react").FunctionComponentElement<DrawingProps>) | undefined;
|
|
71
|
+
export type { SanityIconConfig, SanityIconDefinition, SanityIconName, SanityIconOptions };
|
|
20
72
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.ts","../src/components/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.ts","../src/lib/nodes.ts","../src/components/drawing.tsx","../src/lib/preview.ts"],"mappings":";;;;;YAQY;IACR,MAAM;;;;;;;;;KAUE,8BAA8B;;;;;;UAOzB,0BAA0B;;EAEzC,QAAQ;;;;;;;;UASO,6BAA6B,KAAK,2BAA2B;EAC5E,UAAU;;;;;;;UAQK;;EAEf,QAAQ;;;;;;;;;;wBAkBM,WAAW,SAAQ,oCAAqB;;cC9DlD;;KAGD,eAAe,iBAAiB,uBAAuB,YAAY;;KAG5D,WAAW;KCAX,eAAe,KAAK;EAAuC,MAAM;;;;;;;;;;wBCM7D,kBAAkB,OAAO,gDAAsB,yBAAtB"}
|