@web-portfolio/icons-sanity 3.0.3 → 3.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.
Files changed (3) hide show
  1. package/README.md +51 -8
  2. package/dist/index.js +116 -72
  3. package/package.json +6 -5
package/README.md CHANGED
@@ -56,10 +56,9 @@ you from doing that again:
56
56
  alongside its label, category, and the exact string that got stored — so
57
57
  editors can confirm at a glance they picked the right one.
58
58
  - **Built entirely from Sanity UI.** `Card`, `Dialog`, `Badge`, `Select`,
59
- `TextInput`, `Tooltip` and `@sanity/icons`, sized off the Sanity UI space
60
- scale. It inherits your Studio's theme and light/dark scheme instead of
61
- fighting it, and respects `readOnly` fields and Studio's focus/presence
62
- tracking like a built-in input.
59
+ `TextInput`, sized off the Sanity UI space scale. It inherits your Studio's
60
+ theme and light/dark scheme instead of fighting it, and respects `readOnly`
61
+ fields and Studio's focus/presence tracking like a built-in input.
63
62
 
64
63
  ## Installation
65
64
 
@@ -72,10 +71,11 @@ pnpm add @web-portfolio/icons-sanity
72
71
  ```
73
72
 
74
73
  Peer dependencies:
75
- `sanity >=3`, `@sanity/ui >=2`, `@sanity/icons >=3`, `react >=18`,
76
- `styled-components >=6` (already present in any standard Sanity Studio v3
77
- project), plus `@web-portfolio/icons >=1.0.1`, which renders the icons this
78
- plugin picks — install it if your Studio doesn't already have it.
74
+ `sanity >=3`, `@sanity/ui >=2`, `react >=18`, `styled-components >=6`
75
+ (already present in any standard Sanity Studio project — this plugin is
76
+ tested against every Studio major from v3 through the current v6), plus
77
+ `@web-portfolio/icons >=1.0.1`, which renders the icons this plugin picks —
78
+ install it if your Studio doesn't already have it.
79
79
 
80
80
  ## Setup
81
81
 
@@ -231,6 +231,49 @@ schema.
231
231
 
232
232
  ## Changelog
233
233
 
234
+ **v3.1.0 — Sanity Studio v6 compatibility**
235
+ - **`@sanity/icons` is no longer a dependency.** It dropped its root barrel
236
+ export in v5.0.0 — `import {ImageIcon} from '@sanity/icons'` stopped
237
+ resolving — and Sanity Studio v6 (the current major) already installs
238
+ `@sanity/icons` ^5.2 for itself, so this plugin would fail to load in any
239
+ new v6 Studio. Its four UI-chrome icons (search, trash, warning, the "no
240
+ icon" placeholder) are now inlined instead — visually identical, zero
241
+ version dependency.
242
+ - **Fixed layout on `@sanity/ui` v4.** `Stack`'s `space` prop and `Grid`'s
243
+ `columns` prop were both removed there (in favor of `gap` and
244
+ `gridTemplateColumns`) — silently, with no error, just missing spacing and
245
+ a non-responsive icon grid. Both are replaced with plain CSS (`gap` via a
246
+ specificity override, a `minmax()`-based grid) that doesn't depend on which
247
+ major is installed.
248
+ - **`Tooltip` is no longer imported from `@sanity/ui`.** It moved to the
249
+ `@sanity/ui/tooltip` subpath in v4 — a subpath that doesn't exist in v2/v3
250
+ — so importing it from the root, as this plugin used to, resolves to
251
+ `undefined` on v4 and crashes the results grid the moment it renders. The
252
+ per-tile tooltip (showing an icon's raw registry name on hover) is now a
253
+ native `title` attribute instead; `aria-label` already carried the same
254
+ information for assistive tech.
255
+ - Verified against real installs of every Studio major still actively
256
+ maintained — v3, v4, v5, and v6 — not just against the changelogs above.
257
+ v6 in particular installs `@sanity/ui` 4.0.7 and `sanity` itself pulls in
258
+ React 19.2 and `@sanity/icons` 5.2, the exact combination the fixes above
259
+ target; the full test suite passes against that real combination, not just
260
+ this package's own (older) pinned dev dependencies.
261
+
262
+ **Icon picker: grid fixes + reliable auto-focus**
263
+ - **Fixed overlap:** grid tiles' icon row was sized off an unrelated Sanity UI
264
+ theme token, which was shorter than the icon itself — the glyph visually
265
+ spilled down over its own label. The row now sizes off the actual icon
266
+ dimension, so this can't happen regardless of icon or theme.
267
+ - **Icons are 20% bigger** in the grid (24px → 29px) for better legibility.
268
+ - **Fewer icons per page** (96 → 48), matching the larger tiles so a page
269
+ still renders as a comfortable handful of rows.
270
+ - **Search field now reliably auto-focuses** when the dialog opens. Sanity
271
+ UI's `Dialog` unconditionally focuses its own header close button on mount
272
+ (it's the first focusable element in the DOM), which was winning a race
273
+ against the search field's `autoFocus` — the field now explicitly reclaims
274
+ focus after Dialog's own mount behavior runs, so opening the picker always
275
+ drops you straight into search.
276
+
234
277
  **v3.0.0 — ESM-only, built with `@sanity/plugin-kit`**
235
278
  - The build now uses [`@sanity/plugin-kit`](https://github.com/sanity-io/plugins/tree/main/packages/@sanity/plugin-kit)
236
279
  and [`@sanity/pkg-utils`](https://github.com/sanity-io/pkg-utils) instead
package/dist/index.js CHANGED
@@ -1,11 +1,38 @@
1
1
  import { definePlugin, defineType, set, unset } from "sanity";
2
- import { useCallback, useEffect, useMemo, useState } from "react";
3
- import { Badge, Box, Button, Card, Dialog, Flex, Grid, Select, Stack, Text, TextInput, Tooltip, rem } from "@sanity/ui";
4
- import { ImageIcon, SearchIcon, TrashIcon, WarningOutlineIcon } from "@sanity/icons";
2
+ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { Badge, Box, Button, Card, Dialog, Flex, Grid, Select, Stack, Text, TextInput, rem } from "@sanity/ui";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { styled } from "styled-components";
6
6
  import { Icon } from "@web-portfolio/icons";
7
- import { jsx, jsxs } from "react/jsx-runtime";
8
- const metadata = {
7
+ /**
8
+ * Inlined rather than imported from `@sanity/icons`. That package dropped its
9
+ * root barrel export in v5 — `import {ImageIcon} from '@sanity/icons'` stops
10
+ * resolving entirely — and Sanity Studio v6 already pulls `@sanity/icons`
11
+ * ^5.2 as its own dependency, so any Studio on the current major would fail
12
+ * to load this plugin. These four are visually identical to their
13
+ * `@sanity/icons` counterparts (same viewBox, path data and stroke), just
14
+ * copied in so this plugin has no dependency on which major is installed.
15
+ */
16
+ function iconPath(d) {
17
+ return forwardRef(function IconSvg(props, ref) {
18
+ return /* @__PURE__ */ jsx("svg", {
19
+ width: "1em",
20
+ height: "1em",
21
+ viewBox: "0 0 25 25",
22
+ fill: "none",
23
+ xmlns: "http://www.w3.org/2000/svg",
24
+ ...props,
25
+ ref,
26
+ children: /* @__PURE__ */ jsx("path", {
27
+ d,
28
+ stroke: "currentColor",
29
+ strokeWidth: 1.2,
30
+ strokeLinejoin: "round"
31
+ })
32
+ });
33
+ });
34
+ }
35
+ const ImageIcon = iconPath("M5.5 15.5L8.79289 12.2071C9.18342 11.8166 9.81658 11.8166 10.2071 12.2071L12.8867 14.8867C13.2386 15.2386 13.7957 15.2782 14.1938 14.9796L15.1192 14.2856C15.3601 14.1049 15.6696 14.0424 15.9618 14.1154L19.5 15M5.5 6.5H19.5V18.5H5.5V6.5ZM15.5 10.5C15.5 11.0523 15.0523 11.5 14.5 11.5C13.9477 11.5 13.5 11.0523 13.5 10.5C13.5 9.94772 13.9477 9.5 14.5 9.5C15.0523 9.5 15.5 9.94772 15.5 10.5Z"), SearchIcon = iconPath("M15.0355 15.0355L20 20M16.5 11.5C16.5 14.2614 14.2614 16.5 11.5 16.5C8.73858 16.5 6.5 14.2614 6.5 11.5C6.5 8.73858 8.73858 6.5 11.5 6.5C14.2614 6.5 16.5 8.73858 16.5 11.5Z"), TrashIcon = iconPath("M5 6.5H20M10 6.5V4.5C10 3.94772 10.4477 3.5 11 3.5H14C14.5523 3.5 15 3.94772 15 4.5V6.5M12.5 9V17M15.5 9L15 17M9.5 9L10 17M18.5 6.5L17.571 18.5767C17.5309 19.0977 17.0965 19.5 16.574 19.5H8.42603C7.90349 19.5 7.46905 19.0977 7.42898 18.5767L6.5 6.5H18.5Z"), WarningOutlineIcon = iconPath("M12.5 9V13M12.5 16V14.5M14.2239 5.43058L20.727 16.486C21.5113 17.8192 20.55 19.5 19.0032 19.5H5.99683C4.45 19.5 3.48869 17.8192 4.27297 16.486L10.7761 5.43058C11.5494 4.11596 13.4506 4.11596 14.2239 5.43058Z"), metadata = {
9
36
  aarch64: {
10
37
  label: "Aarch64",
11
38
  tags: [
@@ -4822,6 +4849,30 @@ const metadata = {
4822
4849
  ],
4823
4850
  category: "original"
4824
4851
  },
4852
+ "verdict-check": {
4853
+ label: "Verdict Check",
4854
+ tags: [
4855
+ "check",
4856
+ "checkmark",
4857
+ "yes",
4858
+ "verdict",
4859
+ "tick",
4860
+ "comparison"
4861
+ ],
4862
+ category: "original"
4863
+ },
4864
+ "verdict-cross": {
4865
+ label: "Verdict Cross",
4866
+ tags: [
4867
+ "cross",
4868
+ "close",
4869
+ "no",
4870
+ "verdict",
4871
+ "x",
4872
+ "comparison"
4873
+ ],
4874
+ category: "original"
4875
+ },
4825
4876
  vertx: {
4826
4877
  label: "Vertx",
4827
4878
  tags: [
@@ -5194,22 +5245,28 @@ const metadata = {
5194
5245
  }
5195
5246
  }, PreviewFrame = styled(Card).withConfig({
5196
5247
  displayName: "PreviewFrame",
5197
- componentId: "sc-0esdoi-0"
5248
+ componentId: "sc-7yumqh-0"
5198
5249
  })`flex-shrink:0;width:${({ theme }) => rem(28 + theme.sanity.space[3] * 2)};height:${({ theme }) => rem(28 + theme.sanity.space[3] * 2)};`, ResultsScroller = styled(Box).withConfig({
5199
5250
  displayName: "ResultsScroller",
5200
- componentId: "sc-0esdoi-1"
5201
- })`max-height:${rem(400)};overflow-y:auto;overscroll-behavior:contain;`, IconTile = styled(Card).withConfig({
5251
+ componentId: "sc-7yumqh-1"
5252
+ })`max-height:${rem(400)};overflow-y:auto;overscroll-behavior:contain;`, VStack = styled(Stack).withConfig({
5253
+ displayName: "VStack",
5254
+ componentId: "sc-7yumqh-2"
5255
+ })`&&{gap:${({ theme, $space }) => rem(theme.sanity.space[$space])};}`, ResultsGrid = styled(Grid).withConfig({
5256
+ displayName: "ResultsGrid",
5257
+ componentId: "sc-7yumqh-3"
5258
+ })`grid-template-columns:repeat(auto-fill,minmax(${rem(88)},1fr));`, IconTile = styled(Card).withConfig({
5202
5259
  displayName: "IconTile",
5203
- componentId: "sc-0esdoi-2"
5260
+ componentId: "sc-7yumqh-4"
5204
5261
  })`&&[data-as='button']{cursor:pointer;border:1px solid var(--card-border-color);min-width:0;overflow:hidden;}`, GlyphRow = styled(Flex).withConfig({
5205
5262
  displayName: "GlyphRow",
5206
- componentId: "sc-0esdoi-3"
5263
+ componentId: "sc-7yumqh-5"
5207
5264
  })`height:${rem(37)};`, LabelRow = styled(Box).withConfig({
5208
5265
  displayName: "LabelRow",
5209
- componentId: "sc-0esdoi-4"
5266
+ componentId: "sc-7yumqh-6"
5210
5267
  })`min-height:${rem(20)};`, CategorySelectBox = styled(Box).withConfig({
5211
5268
  displayName: "CategorySelectBox",
5212
- componentId: "sc-0esdoi-5"
5269
+ componentId: "sc-7yumqh-7"
5213
5270
  })`width:${rem(180)};`;
5214
5271
  function matchesQuery(name, entry, query) {
5215
5272
  if (!query.trim()) return !0;
@@ -5242,7 +5299,7 @@ function formatCategoryLabel(category) {
5242
5299
  return CATEGORY_LABEL_OVERRIDES[category] ? CATEGORY_LABEL_OVERRIDES[category] : category.split(/[-_]/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
5243
5300
  }
5244
5301
  function IconPickerInput(props) {
5245
- let { value, onChange, readOnly, elementProps } = props, [open, setOpen] = useState(!1), [query, setQuery] = useState(""), [category, setCategory] = useState(""), [visibleCount, setVisibleCount] = useState(48), names = useMemo(() => Object.keys(metadata).sort(), []), categories = useMemo(() => {
5302
+ let { value, onChange, readOnly, elementProps } = props, [open, setOpen] = useState(!1), [query, setQuery] = useState(""), [category, setCategory] = useState(""), [visibleCount, setVisibleCount] = useState(48), searchInputRef = useRef(null), names = useMemo(() => Object.keys(metadata).sort(), []), categories = useMemo(() => {
5246
5303
  let found = /* @__PURE__ */ new Set();
5247
5304
  for (let name of names) {
5248
5305
  let entry = metadata[name];
@@ -5262,7 +5319,9 @@ function IconPickerInput(props) {
5262
5319
  ]);
5263
5320
  useEffect(() => {
5264
5321
  setVisibleCount(48);
5265
- }, [query, category]);
5322
+ }, [query, category]), useEffect(() => {
5323
+ open && searchInputRef.current?.focus();
5324
+ }, [open]);
5266
5325
  let visible = filtered.slice(0, visibleCount), remaining = filtered.length - visible.length, selectedEntry = value ? metadata[value] : void 0, hasUnknownValue = !!(value && !selectedEntry), closeDialog = useCallback(() => {
5267
5326
  setOpen(!1), setQuery(""), setCategory("");
5268
5327
  }, []), handleSelect = useCallback((name) => {
@@ -5280,8 +5339,8 @@ function IconPickerInput(props) {
5280
5339
  ref: elementProps?.ref,
5281
5340
  "aria-describedby": elementProps?.["aria-describedby"]
5282
5341
  };
5283
- return /* @__PURE__ */ jsxs(Stack, {
5284
- space: 3,
5342
+ return /* @__PURE__ */ jsxs(VStack, {
5343
+ $space: 3,
5285
5344
  children: [
5286
5345
  /* @__PURE__ */ jsxs(Flex, {
5287
5346
  align: "center",
@@ -5307,11 +5366,11 @@ function IconPickerInput(props) {
5307
5366
  children: jsx(hasUnknownValue ? WarningOutlineIcon : ImageIcon, {})
5308
5367
  })
5309
5368
  })
5310
- }), /* @__PURE__ */ jsxs(Stack, {
5311
- space: 3,
5369
+ }), /* @__PURE__ */ jsxs(VStack, {
5370
+ $space: 3,
5312
5371
  flex: 1,
5313
- children: [/* @__PURE__ */ jsxs(Stack, {
5314
- space: 2,
5372
+ children: [/* @__PURE__ */ jsxs(VStack, {
5373
+ $space: 2,
5315
5374
  children: [/* @__PURE__ */ jsxs(Flex, {
5316
5375
  align: "center",
5317
5376
  gap: 2,
@@ -5388,13 +5447,14 @@ function IconPickerInput(props) {
5388
5447
  }),
5389
5448
  children: /* @__PURE__ */ jsx(Box, {
5390
5449
  padding: 4,
5391
- children: /* @__PURE__ */ jsxs(Stack, {
5392
- space: 4,
5450
+ children: /* @__PURE__ */ jsxs(VStack, {
5451
+ $space: 4,
5393
5452
  children: [/* @__PURE__ */ jsxs(Flex, {
5394
5453
  gap: 2,
5395
5454
  children: [/* @__PURE__ */ jsx(Box, {
5396
5455
  flex: 1,
5397
5456
  children: /* @__PURE__ */ jsx(TextInput, {
5457
+ ref: searchInputRef,
5398
5458
  icon: SearchIcon,
5399
5459
  placeholder: "Search by name, label, or tag…",
5400
5460
  value: query,
@@ -5421,8 +5481,8 @@ function IconPickerInput(props) {
5421
5481
  padding: 5,
5422
5482
  radius: 2,
5423
5483
  tone: "transparent",
5424
- children: /* @__PURE__ */ jsxs(Stack, {
5425
- space: 3,
5484
+ children: /* @__PURE__ */ jsxs(VStack, {
5485
+ $space: 3,
5426
5486
  children: [/* @__PURE__ */ jsx(Text, {
5427
5487
  align: "center",
5428
5488
  size: 1,
@@ -5435,57 +5495,41 @@ function IconPickerInput(props) {
5435
5495
  children: "Try a shorter term, or reset the category filter."
5436
5496
  })]
5437
5497
  })
5438
- }) : /* @__PURE__ */ jsx(ResultsScroller, { children: /* @__PURE__ */ jsxs(Stack, {
5439
- space: 3,
5440
- children: [/* @__PURE__ */ jsx(Grid, {
5441
- columns: [
5442
- 3,
5443
- 4,
5444
- 6
5445
- ],
5498
+ }) : /* @__PURE__ */ jsx(ResultsScroller, { children: /* @__PURE__ */ jsxs(VStack, {
5499
+ $space: 3,
5500
+ children: [/* @__PURE__ */ jsx(ResultsGrid, {
5446
5501
  gap: 2,
5447
5502
  children: visible.map((name) => {
5448
5503
  let entry = metadata[name], isSelected = name === value;
5449
- return /* @__PURE__ */ jsx(Tooltip, {
5450
- content: /* @__PURE__ */ jsx(Box, {
5451
- padding: 2,
5452
- children: /* @__PURE__ */ jsx(Text, {
5453
- size: 1,
5454
- children: name
5455
- })
5456
- }),
5457
- placement: "top",
5458
- delay: { open: 400 },
5459
- portal: !0,
5460
- children: /* @__PURE__ */ jsx(IconTile, {
5461
- forwardedAs: "button",
5462
- type: "button",
5463
- padding: 3,
5464
- radius: 2,
5465
- border: !0,
5466
- tone: isSelected ? "primary" : "default",
5467
- pressed: isSelected,
5468
- __unstable_focusRing: !0,
5469
- "aria-pressed": isSelected,
5470
- "aria-label": `${entry.label} (${name})`,
5471
- onClick: () => handleSelect(name),
5472
- children: /* @__PURE__ */ jsxs(Stack, {
5473
- space: 2,
5474
- children: [/* @__PURE__ */ jsx(GlyphRow, {
5475
- align: "center",
5476
- justify: "center",
5477
- children: /* @__PURE__ */ jsx(Icon, {
5478
- name,
5479
- size: 29
5480
- })
5481
- }), /* @__PURE__ */ jsx(LabelRow, { children: /* @__PURE__ */ jsx(Text, {
5482
- size: 0,
5483
- align: "center",
5484
- muted: !0,
5485
- textOverflow: "ellipsis",
5486
- children: entry.label
5487
- }) })]
5488
- })
5504
+ return /* @__PURE__ */ jsx(IconTile, {
5505
+ forwardedAs: "button",
5506
+ type: "button",
5507
+ padding: 3,
5508
+ radius: 2,
5509
+ border: !0,
5510
+ tone: isSelected ? "primary" : "default",
5511
+ pressed: isSelected,
5512
+ __unstable_focusRing: !0,
5513
+ title: name,
5514
+ "aria-pressed": isSelected,
5515
+ "aria-label": `${entry.label} (${name})`,
5516
+ onClick: () => handleSelect(name),
5517
+ children: /* @__PURE__ */ jsxs(VStack, {
5518
+ $space: 2,
5519
+ children: [/* @__PURE__ */ jsx(GlyphRow, {
5520
+ align: "center",
5521
+ justify: "center",
5522
+ children: /* @__PURE__ */ jsx(Icon, {
5523
+ name,
5524
+ size: 29
5525
+ })
5526
+ }), /* @__PURE__ */ jsx(LabelRow, { children: /* @__PURE__ */ jsx(Text, {
5527
+ size: 0,
5528
+ align: "center",
5529
+ muted: !0,
5530
+ textOverflow: "ellipsis",
5531
+ children: entry.label
5532
+ }) })]
5489
5533
  })
5490
5534
  }, name);
5491
5535
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@web-portfolio/icons-sanity",
3
- "version": "3.0.3",
4
- "description": "Sanity Studio icon picker input, backed by the same bundled registry as @web-portfolio/icons.",
3
+ "version": "3.1.0",
4
+ "description": "Sanity Studio icon picker field with 633 bundled SVG icons — search by name, label, or tag, no SVG markup required.",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "sanity",
@@ -11,7 +11,10 @@
11
11
  "icons",
12
12
  "cms",
13
13
  "custom input",
14
- "sanity field"
14
+ "sanity field",
15
+ "sanity-studio",
16
+ "svg-icons",
17
+ "schema-type"
15
18
  ],
16
19
  "homepage": "https://github.com/jatinrao/icons#readme",
17
20
  "bugs": {
@@ -52,7 +55,6 @@
52
55
  "link-watch": "plugin-kit link-watch"
53
56
  },
54
57
  "peerDependencies": {
55
- "@sanity/icons": ">=3",
56
58
  "@sanity/ui": ">=2",
57
59
  "@web-portfolio/icons": ">=1.0.1",
58
60
  "react": ">=18",
@@ -61,7 +63,6 @@
61
63
  },
62
64
  "devDependencies": {
63
65
  "@sanity/browserslist-config": "^1.0.5",
64
- "@sanity/icons": "^3.8.0",
65
66
  "@sanity/pkg-utils": "^12.3.2",
66
67
  "@sanity/plugin-kit": "^10.0.8",
67
68
  "@sanity/ui": "^2.10.0",