@driftime/sanity-plugin-icon 0.2.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 +156 -120
- package/dist/{drawing-Bo0HTICv.js → drawing-BThiR2bN.js} +14 -5
- package/dist/drawing-BThiR2bN.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +299 -218
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +4 -4
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +1 -2
- package/dist/render.js.map +1 -1
- package/dist/types-_D2jGm25.d.ts.map +1 -1
- package/package.json +21 -30
- package/LICENSE +0 -21
- package/dist/drawing-Bo0HTICv.js.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,139 +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). |
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
The type name is always `icon`, so a Studio that already has a type by that name needs to rename it before installing.
|
|
71
93
|
|
|
72
94
|
<br />
|
|
73
95
|
|
|
74
|
-
|
|
96
|
+
### Restricting the Icons
|
|
75
97
|
|
|
76
|
-
|
|
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.
|
|
77
99
|
|
|
78
100
|
```typescript
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
plugins: [iconPlugin({ icons: ["mail", "phone", "globe"] })],
|
|
101
|
+
iconPlugin({
|
|
102
|
+
icons: ["sun", "wind", "droplets", "leaf", "sprout", "mountain", "tent", "compass"],
|
|
82
103
|
});
|
|
83
104
|
```
|
|
84
105
|
|
|
85
|
-
|
|
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`.
|
|
86
107
|
|
|
87
108
|
```typescript
|
|
88
109
|
defineField({
|
|
89
110
|
name: "icon",
|
|
90
111
|
type: "icon",
|
|
91
|
-
description: "
|
|
92
|
-
options: { icons: ["
|
|
112
|
+
description: "Icon shown beside the contact method.",
|
|
113
|
+
options: { icons: ["mail", "phone", "globe"] },
|
|
93
114
|
});
|
|
94
115
|
```
|
|
95
116
|
|
|
96
|
-
|
|
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.
|
|
97
118
|
|
|
98
|
-
|
|
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`.
|
|
99
120
|
|
|
100
121
|
```typescript
|
|
101
122
|
import type { SanityIconName } from "@driftime/sanity-plugin-icon";
|
|
102
123
|
|
|
103
|
-
export const
|
|
124
|
+
export const contactIcons: SanityIconName[] = ["mail", "phone", "globe"];
|
|
104
125
|
```
|
|
105
126
|
|
|
106
|
-
Restricting a set does not affect what has already been stored. The document holds the drawing, so an icon saved earlier still renders even though the picker no longer offers it.
|
|
107
|
-
|
|
108
|
-
<br />
|
|
109
|
-
|
|
110
|
-
## Picking an Icon
|
|
111
|
-
|
|
112
|
-
- Search matches an icon's name and the terms it is tagged with, so "next" finds `arrow-right`.
|
|
113
|
-
- The complete Lucide set is offered unless you restrict it, and the count reflects whatever is available.
|
|
114
|
-
- The last eight icons chosen are remembered, per browser rather than per dataset.
|
|
115
|
-
|
|
116
127
|
<br />
|
|
117
128
|
|
|
118
129
|
<figure>
|
|
119
130
|
<picture>
|
|
120
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
121
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
122
|
-
<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" />
|
|
123
134
|
</picture>
|
|
124
|
-
<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>
|
|
125
136
|
</figure>
|
|
126
137
|
|
|
127
138
|
<br />
|
|
128
139
|
|
|
129
|
-
|
|
140
|
+
### Document Previews
|
|
130
141
|
|
|
131
|
-
|
|
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`.
|
|
132
143
|
|
|
133
|
-
```
|
|
134
|
-
import {
|
|
144
|
+
```typescript
|
|
145
|
+
import { createIconPreview } from "@driftime/sanity-plugin-icon";
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
defineType({
|
|
148
|
+
// ...
|
|
149
|
+
preview: {
|
|
150
|
+
select: { title: "title", icon: "icon" },
|
|
151
|
+
prepare({ title, icon }) {
|
|
152
|
+
return { title, media: createIconPreview(icon) };
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
137
156
|
```
|
|
138
157
|
|
|
139
|
-
|
|
158
|
+
When nothing readable is stored, it returns `undefined` and the preview falls back to the type's own icon.
|
|
140
159
|
|
|
141
|
-
|
|
160
|
+
<br />
|
|
142
161
|
|
|
143
|
-
|
|
144
|
-
import type { SanityIcon } from "@driftime/sanity-plugin-icon";
|
|
162
|
+
### Keeping the Library Current
|
|
145
163
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bun remove @driftime/sanity-plugin-icon
|
|
168
|
+
bun add -E @driftime/sanity-plugin-icon
|
|
150
169
|
```
|
|
151
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
|
+
|
|
152
173
|
<br />
|
|
153
174
|
|
|
154
|
-
##
|
|
175
|
+
## Site
|
|
155
176
|
|
|
156
|
-
|
|
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.
|
|
157
178
|
|
|
158
|
-
|
|
159
|
-
import { createIconPreview } from "@driftime/sanity-plugin-icon";
|
|
179
|
+
<br />
|
|
160
180
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
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
|
+
}
|
|
167
202
|
```
|
|
168
203
|
|
|
169
|
-
|
|
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
|
+
```
|
|
170
213
|
|
|
171
214
|
<br />
|
|
172
215
|
|
|
173
|
-
|
|
216
|
+
### Querying
|
|
174
217
|
|
|
175
|
-
|
|
218
|
+
An icon needs nothing special in a query. It comes back as a plain object with the rest of the document.
|
|
176
219
|
|
|
177
220
|
```groq
|
|
178
221
|
*[_type == "home"][0] { title, icon }
|
|
@@ -180,81 +223,74 @@ A stored icon needs no special handling in a query. It comes down as a plain obj
|
|
|
180
223
|
|
|
181
224
|
<br />
|
|
182
225
|
|
|
183
|
-
##
|
|
226
|
+
## Reference
|
|
184
227
|
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
"_type": "icon",
|
|
188
|
-
"name": "house",
|
|
189
|
-
"node": "[[\"path\",{\"d\":\"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8\"}], …]"
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
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.
|
|
228
|
+
The rest of this document covers the shape of a stored icon and everything the package exports.
|
|
194
229
|
|
|
195
230
|
<br />
|
|
196
231
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
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.
|
|
232
|
+
### Stored Icons
|
|
200
233
|
|
|
201
|
-
|
|
202
|
-
bun remove @driftime/sanity-plugin-icon
|
|
203
|
-
bun add -E @driftime/sanity-plugin-icon
|
|
204
|
-
```
|
|
234
|
+
`SanityIcon` is the type of a stored icon.
|
|
205
235
|
|
|
206
|
-
|
|
236
|
+
```typescript
|
|
237
|
+
import type { SanityIcon } from "@driftime/sanity-plugin-icon/render";
|
|
207
238
|
|
|
208
|
-
|
|
239
|
+
export interface SanityFeature {
|
|
240
|
+
icon?: SanityIcon;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
209
243
|
|
|
210
|
-
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"_type": "icon",
|
|
247
|
+
"name": "house",
|
|
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\"}]]"
|
|
249
|
+
}
|
|
250
|
+
```
|
|
211
251
|
|
|
212
|
-
|
|
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. |
|
|
213
257
|
|
|
214
|
-
|
|
215
|
-
| -------------------------- | ------------------------------------- | ------------------------------------------ |
|
|
216
|
-
| `iconPlugin(config?)` | `@driftime/sanity-plugin-icon` | Registers the `icon` type with the Studio. |
|
|
217
|
-
| `createIconPreview(value)` | `@driftime/sanity-plugin-icon` | Builds preview media from a stored icon. |
|
|
218
|
-
| `Icon` | `@driftime/sanity-plugin-icon/render` | Draws a stored icon. |
|
|
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.
|
|
219
259
|
|
|
220
260
|
<br />
|
|
221
261
|
|
|
222
|
-
|
|
262
|
+
### API
|
|
223
263
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
| `
|
|
229
|
-
|
|
230
|
-
A field accepts the same option under `options`, where it replaces the set the plugin was given.
|
|
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. |
|
|
231
269
|
|
|
232
270
|
<br />
|
|
233
271
|
|
|
234
|
-
|
|
272
|
+
### Types
|
|
235
273
|
|
|
236
|
-
|
|
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. |
|
|
237
282
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
| Type | Purpose |
|
|
241
|
-
| ---------------------- | ------------------------------------------------------- |
|
|
242
|
-
| `SanityIconName` | The name of an icon in the installed Lucide version. |
|
|
243
|
-
| `SanityIconOptions` | The `options` an icon field accepts. |
|
|
244
|
-
| `SanityIconDefinition` | The shape of a field or array member of type `icon`. |
|
|
245
|
-
| `SanityIconConfig` | The full plugin configuration accepted by `iconPlugin`. |
|
|
283
|
+
<br />
|
|
246
284
|
|
|
247
|
-
|
|
285
|
+
## License
|
|
248
286
|
|
|
249
|
-
|
|
250
|
-
| ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
251
|
-
| `SanityIcon` | A stored icon, holding the name it was chosen by and the shapes it draws. Available from either import path. |
|
|
287
|
+
MIT © [Driftime®](https://driftime.com). See [LICENSE](https://github.com/driftime/sanity-plugins/blob/main/LICENSE).
|
|
252
288
|
|
|
253
289
|
<br />
|
|
254
290
|
|
|
255
291
|
## Acknowledgements
|
|
256
292
|
|
|
257
|
-
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.
|
|
258
294
|
|
|
259
295
|
<br />
|
|
260
296
|
<br />
|
|
@@ -262,7 +298,7 @@ Icons come from [Lucide](https://lucide.dev), distributed under the [ISC License
|
|
|
262
298
|
<div align="center">
|
|
263
299
|
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
264
300
|
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
265
|
-
<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>
|
|
266
302
|
</div>
|
|
267
303
|
|
|
268
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
|
@@ -49,7 +49,7 @@ interface SanityIconConfig {
|
|
|
49
49
|
* @returns Sanity plugin definition.
|
|
50
50
|
* @public
|
|
51
51
|
*/
|
|
52
|
-
declare function iconPlugin(config?: SanityIconConfig): import("sanity").PluginOptions;
|
|
52
|
+
export declare function iconPlugin(config?: SanityIconConfig): import("sanity").PluginOptions;
|
|
53
53
|
/** Elements an icon's drawing may be built from, narrowed to those that describe a shape. */
|
|
54
54
|
declare const iconElements: readonly ["circle", "ellipse", "g", "line", "path", "polygon", "polyline", "rect"];
|
|
55
55
|
/** Single shape in an icon's drawing, paired with the attributes it is drawn with. */
|
|
@@ -67,6 +67,6 @@ type DrawingProps = Omit<ComponentProps<"svg">, "children"> & {
|
|
|
67
67
|
* @returns The media component, or undefined when nothing readable was stored.
|
|
68
68
|
* @public
|
|
69
69
|
*/
|
|
70
|
-
declare function createIconPreview(value: SanityIcon | undefined): (() => import("react").FunctionComponentElement<DrawingProps>) | undefined;
|
|
71
|
-
export
|
|
70
|
+
export declare function createIconPreview(value: SanityIcon | undefined): (() => import("react").FunctionComponentElement<DrawingProps>) | undefined;
|
|
71
|
+
export type { SanityIconConfig, SanityIconDefinition, SanityIconName, SanityIconOptions };
|
|
72
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/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;;;;;;;;;;
|
|
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"}
|