@nice2dev/icons 1.3.7 → 1.3.9
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/CHANGELOG.md +90 -0
- package/README.md +33 -16
- package/dist/cjs/NiceIconPicker.js +289 -92
- package/dist/cjs/NiceIconStacker.js +137 -108
- package/dist/cjs/client.js +6 -0
- package/dist/cjs/createIcon.js +17 -4
- package/dist/cjs/iconHtml.js +122 -0
- package/dist/cjs/iconParts.js +24 -2
- package/dist/cjs/iconStack.js +76 -5
- package/dist/cjs/icons/accessibility.js +9 -8
- package/dist/cjs/icons/actions.js +38 -35
- package/dist/cjs/icons/ai.js +12 -11
- package/dist/cjs/icons/brand.js +2 -2
- package/dist/cjs/icons/business.js +14 -16
- package/dist/cjs/icons/devtech.js +17 -15
- package/dist/cjs/icons/ecommerce.js +7 -12
- package/dist/cjs/icons/education.js +14 -16
- package/dist/cjs/icons/files.js +46 -22
- package/dist/cjs/icons/fintech.js +2 -11
- package/dist/cjs/icons/food.js +12 -8
- package/dist/cjs/icons/fun.js +11 -11
- package/dist/cjs/icons/gaming.js +20 -13
- package/dist/cjs/icons/health.js +9 -12
- package/dist/cjs/icons/index.js +1 -0
- package/dist/cjs/icons/input.js +31 -21
- package/dist/cjs/icons/legal.js +11 -11
- package/dist/cjs/icons/media.js +24 -15
- package/dist/cjs/icons/navigation.js +16 -13
- package/dist/cjs/icons/omniverk.js +2 -7
- package/dist/cjs/icons/realestate.js +14 -14
- package/dist/cjs/icons/saas.js +0 -10
- package/dist/cjs/icons/science.js +12 -12
- package/dist/cjs/icons/shapes.js +20 -17
- package/dist/cjs/icons/smarthome.js +0 -11
- package/dist/cjs/icons/social.js +7 -6
- package/dist/cjs/icons/sports.js +5 -6
- package/dist/cjs/icons/sustainability.js +12 -11
- package/dist/cjs/icons/travel.js +9 -9
- package/dist/cjs/icons/ui.js +55 -31
- package/dist/cjs/icons/weather.js +8 -13
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/nicetodev-icons.css +1 -1
- package/dist/cjs/resolver.js +1 -0
- package/dist/cjs/stacking.js +38 -11
- package/dist/esm/NiceIconPicker.js +292 -95
- package/dist/esm/NiceIconStacker.js +141 -112
- package/dist/esm/client.js +2 -1
- package/dist/esm/createIcon.js +18 -5
- package/dist/esm/iconHtml.js +117 -0
- package/dist/esm/iconParts.js +24 -3
- package/dist/esm/iconStack.js +74 -6
- package/dist/esm/icons/accessibility.js +9 -8
- package/dist/esm/icons/actions.js +39 -36
- package/dist/esm/icons/ai.js +12 -11
- package/dist/esm/icons/brand.js +2 -2
- package/dist/esm/icons/business.js +15 -17
- package/dist/esm/icons/devtech.js +17 -15
- package/dist/esm/icons/ecommerce.js +7 -12
- package/dist/esm/icons/education.js +14 -16
- package/dist/esm/icons/files.js +47 -23
- package/dist/esm/icons/fintech.js +2 -11
- package/dist/esm/icons/food.js +12 -8
- package/dist/esm/icons/fun.js +11 -11
- package/dist/esm/icons/gaming.js +21 -14
- package/dist/esm/icons/health.js +9 -12
- package/dist/esm/icons/index.js +1 -1
- package/dist/esm/icons/input.js +31 -21
- package/dist/esm/icons/legal.js +12 -12
- package/dist/esm/icons/media.js +25 -16
- package/dist/esm/icons/navigation.js +16 -13
- package/dist/esm/icons/omniverk.js +2 -7
- package/dist/esm/icons/realestate.js +15 -15
- package/dist/esm/icons/saas.js +0 -10
- package/dist/esm/icons/science.js +12 -12
- package/dist/esm/icons/shapes.js +20 -17
- package/dist/esm/icons/smarthome.js +0 -11
- package/dist/esm/icons/social.js +8 -7
- package/dist/esm/icons/sports.js +6 -7
- package/dist/esm/icons/sustainability.js +12 -11
- package/dist/esm/icons/travel.js +10 -10
- package/dist/esm/icons/ui.js +56 -33
- package/dist/esm/icons/weather.js +9 -14
- package/dist/esm/index.js +3 -2
- package/dist/esm/nicetodev-icons.css +1 -1
- package/dist/esm/resolver.js +1 -0
- package/dist/esm/stacking.js +36 -12
- package/dist/types/NiceIconPicker.d.ts +66 -20
- package/dist/types/NiceIconStacker.d.ts +30 -9
- package/dist/types/client.d.ts +4 -2
- package/dist/types/createIcon.d.ts +16 -2
- package/dist/types/iconHtml.d.ts +44 -0
- package/dist/types/iconParts.d.ts +8 -2
- package/dist/types/iconStack.d.ts +30 -3
- package/dist/types/icons/index.d.ts +1 -1
- package/dist/types/icons/ui.d.ts +1 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/stacking.d.ts +25 -5
- package/dist/types/types.d.ts +11 -7
- package/package.json +2 -2
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useRovingFocus, useNiceTranslation, useOverlayFocus } from '@nice2dev/ui-core';
|
|
3
|
-
import { forwardRef, useRef, useState, useMemo, useCallback, useImperativeHandle, useEffect } from 'react';
|
|
2
|
+
import { useRovingFocus, useNiceTranslation, useControllableState, useOverlayFocus } from '@nice2dev/ui-core';
|
|
3
|
+
import { forwardRef, useRef, useState, useId, useMemo, useCallback, useImperativeHandle, useEffect, useLayoutEffect } from 'react';
|
|
4
|
+
import { parseIconHtml, scopeIconHtmlIds, iconHtmlAttributes } from './iconHtml.js';
|
|
4
5
|
import { NtdSearch, NtdCheck } from './icons/actions.js';
|
|
5
6
|
import { NtdGrid } from './icons/files.js';
|
|
6
7
|
import { NtdInbox } from './icons/media.js';
|
|
7
|
-
import { NtdChevronDown } from './icons/navigation.js';
|
|
8
|
+
import { NtdArrowLeft, NtdChevronDown } from './icons/navigation.js';
|
|
8
9
|
import { NtdStar, NtdClock } from './icons/ui.js';
|
|
10
|
+
import { iconDisplayName, renderIconStack } from './iconStack.js';
|
|
11
|
+
import { NiceIconStacker } from './NiceIconStacker.js';
|
|
9
12
|
|
|
10
13
|
const MAX_HISTORY_ITEMS = 20;
|
|
14
|
+
/** `useLayoutEffect` in the browser, `useEffect` on the server (no warning there). */
|
|
15
|
+
const useIsoLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
|
|
11
16
|
/* ─── Small icons: the package's own (until 1.2.0 these were pasted SVGs) ─── */
|
|
12
17
|
const Chevron = () => jsx(NtdChevronDown, { size: 12, variant: "outline" });
|
|
13
18
|
const SearchIcon = () => jsx(NtdSearch, { size: 14, variant: "outline" });
|
|
14
19
|
const StarIcon = ({ filled }) => (jsx(NtdStar, { size: 12, variant: filled ? 'solid' : 'outline' }));
|
|
15
20
|
const ClockIcon = () => jsx(NtdClock, { size: 12, variant: "outline" });
|
|
16
21
|
const CheckIcon = () => jsx(NtdCheck, { size: 10, variant: "outline" });
|
|
22
|
+
const NO_NAMES = [];
|
|
23
|
+
const NO_LAYERS = [];
|
|
24
|
+
/** The accent the picker draws with: the page's background, white without a theme. */
|
|
25
|
+
const PAGE_ACCENT = 'var(--nice-bg-primary, white)';
|
|
26
|
+
/** An entry's name as shown: its label, else the export name without `Ntd`. */
|
|
27
|
+
const entryLabel = (entry) => { var _a; return (_a = entry.label) !== null && _a !== void 0 ? _a : iconDisplayName(entry.name); };
|
|
28
|
+
/** The part of the window an element's popup can show in: the window, cut by every ancestor that clips or scrolls. */
|
|
29
|
+
function visibleBounds(el) {
|
|
30
|
+
const bounds = { top: 0, bottom: window.innerHeight, left: 0, right: window.innerWidth };
|
|
31
|
+
for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
|
|
32
|
+
const style = window.getComputedStyle(p);
|
|
33
|
+
if (style.overflowX !== 'visible' || style.overflowY !== 'visible') {
|
|
34
|
+
const r = p.getBoundingClientRect();
|
|
35
|
+
bounds.top = Math.max(bounds.top, r.top);
|
|
36
|
+
bounds.bottom = Math.min(bounds.bottom, r.bottom);
|
|
37
|
+
bounds.left = Math.max(bounds.left, r.left);
|
|
38
|
+
bounds.right = Math.min(bounds.right, r.right);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return bounds;
|
|
42
|
+
}
|
|
17
43
|
/* ─── localStorage helpers ─── */
|
|
18
44
|
function loadFromStorage(key, fallback) {
|
|
19
45
|
if (typeof window === 'undefined') {
|
|
@@ -44,40 +70,43 @@ function saveToStorage(key, value) {
|
|
|
44
70
|
/**
|
|
45
71
|
* A fully-featured icon picker dropdown component.
|
|
46
72
|
*
|
|
47
|
-
* Provides a trigger button that opens a searchable, categorized icon grid.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
73
|
+
* Provides a trigger button that opens a searchable, categorized icon grid. The value binds both
|
|
74
|
+
* ways — `value` / `defaultValue` + `onChange` (1.3.9) — as the icon's export name or, with
|
|
75
|
+
* `valueFormat="html"`, as the icon drawn in HTML, ready to store. With `stackable` the panel gets
|
|
76
|
+
* an "Overlays" tab where icons are stacked on the chosen one. Names show without `Ntd`; the
|
|
77
|
+
* trigger shows the icon alone or the icon and its name (`displayMode`). Keyboard: arrows in the
|
|
78
|
+
* grid, Escape to close.
|
|
50
79
|
*
|
|
51
80
|
* **Styling:** Uses CSS variables for theming (see `NiceIconPicker.css`).
|
|
52
81
|
* Override `--ntd-picker-bg`, `--ntd-picker-accent`, etc. on a parent element.
|
|
53
82
|
*
|
|
54
83
|
* @example
|
|
55
84
|
* ```tsx
|
|
56
|
-
* import { NiceIconPicker } from '@
|
|
57
|
-
*
|
|
58
|
-
* function IconSelectField({ icons }) {
|
|
59
|
-
* const [selected, setSelected] = useState<string | null>(null);
|
|
85
|
+
* import { NiceIconPicker } from '@nice2dev/icons';
|
|
60
86
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* value={selected}
|
|
65
|
-
* onChange={(name) => setSelected(name)}
|
|
66
|
-
* variant="solid"
|
|
67
|
-
* placeholder="Pick icon..."
|
|
68
|
-
* />
|
|
69
|
-
* );
|
|
87
|
+
* function IconField({ icons, value, onChange }) {
|
|
88
|
+
* // stores `<svg data-ntd-icon="NtdStar" …>…</svg>`, a stack included
|
|
89
|
+
* return <NiceIconPicker icons={icons} value={value} onChange={onChange} stackable />;
|
|
70
90
|
* }
|
|
71
91
|
* ```
|
|
72
92
|
*/
|
|
73
|
-
const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icons, value
|
|
93
|
+
const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icons, value: valueProp, defaultValue, values: valuesProp, defaultValues, onChange, onChangeMulti, valueFormat: valueFormatProp, stackable = false, displayMode = 'icon-name', optionDisplayMode = 'icon', name, 'aria-label': ariaLabel, id, testId, 'data-testid': dataTestId, multiSelect = false, maxSelections, placeholder, variant = 'solid', iconProps, gridIconSize = 'md', triggerIconSize = 'sm', categories: categoriesProp, className, disabled = false, clearable = true, virtualized = false, virtualizedItemCount = 100, showHistory = false, showFavorites = false, storageKeyPrefix = 'ntd-icon-picker', enableDragAndDrop = false, onDragStart, onDragEnd, }, ref) {
|
|
74
94
|
var _a;
|
|
75
95
|
const tablistRef = useRef(null);
|
|
76
96
|
useRovingFocus(tablistRef, { orientation: 'horizontal', wrap: true, activateOnMove: true });
|
|
97
|
+
const viewsRef = useRef(null);
|
|
98
|
+
useRovingFocus(viewsRef, { orientation: 'horizontal', activateOnMove: true });
|
|
77
99
|
const { t } = useNiceTranslation();
|
|
78
100
|
const [isOpen, setIsOpen] = useState(false);
|
|
79
101
|
const [search, setSearch] = useState('');
|
|
80
102
|
const [activeCategory, setActiveCategory] = useState(null);
|
|
103
|
+
// the panel's view: the icon grid or the stack; `picking` = the grid chooses an icon to stack
|
|
104
|
+
const [view, setView] = useState('icons');
|
|
105
|
+
const [picking, setPicking] = useState(null);
|
|
106
|
+
const panelId = useId();
|
|
107
|
+
const valueFormat = valueFormatProp !== null && valueFormatProp !== void 0 ? valueFormatProp : (stackable ? 'html' : 'name');
|
|
108
|
+
const canStack = stackable && !multiSelect && valueFormat === 'html';
|
|
109
|
+
const drawProps = useMemo(() => ({ accentColor: PAGE_ACCENT, ...iconProps, variant }), [iconProps, variant]);
|
|
81
110
|
// Virtualization state
|
|
82
111
|
const [visibleRange, setVisibleRange] = useState({ start: 0, end: virtualizedItemCount });
|
|
83
112
|
const gridWrapRef = useRef(null);
|
|
@@ -86,35 +115,105 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
86
115
|
const historyKey = `${storageKeyPrefix}-history`;
|
|
87
116
|
const [favorites, setFavorites] = useState(() => loadFromStorage(favoritesKey, []));
|
|
88
117
|
const [history, setHistory] = useState(() => loadFromStorage(historyKey, []));
|
|
89
|
-
//
|
|
90
|
-
const [
|
|
91
|
-
|
|
118
|
+
// The value binds both ways (1.3.9): controlled by `value`, or kept here from `defaultValue`.
|
|
119
|
+
const [value, setValue] = useControllableState({
|
|
120
|
+
value: valueProp,
|
|
121
|
+
defaultValue,
|
|
122
|
+
fallbackValue: null,
|
|
123
|
+
componentName: 'NiceIconPicker',
|
|
124
|
+
});
|
|
125
|
+
const [values, setValues] = useControllableState({
|
|
126
|
+
value: valuesProp,
|
|
127
|
+
defaultValue: defaultValues,
|
|
128
|
+
fallbackValue: NO_NAMES,
|
|
129
|
+
componentName: 'NiceIconPicker',
|
|
130
|
+
});
|
|
92
131
|
const rootRef = useRef(null);
|
|
93
132
|
const searchInputRef = useRef(null);
|
|
133
|
+
const stackerRef = useRef(null);
|
|
134
|
+
const serializerRef = useRef(null);
|
|
135
|
+
/* ─── Icons by name: exact, or without the Ntd a stored value may lack ─── */
|
|
136
|
+
const byName = useMemo(() => new Map(icons.map((e) => [e.name, e])), [icons]);
|
|
137
|
+
const entryOf = useCallback((n) => { var _a; return (n ? ((_a = byName.get(n)) !== null && _a !== void 0 ? _a : byName.get(`Ntd${n}`)) : undefined); }, [byName]);
|
|
138
|
+
const labelOf = useCallback((n) => {
|
|
139
|
+
const entry = entryOf(n);
|
|
140
|
+
return entry ? entryLabel(entry) : iconDisplayName(n);
|
|
141
|
+
}, [entryOf]);
|
|
142
|
+
const resolve = useCallback((n) => { var _a; return (_a = entryOf(n)) === null || _a === void 0 ? void 0 : _a.Component; }, [entryOf]);
|
|
143
|
+
/* ─── The single value as a stack — a plain icon is a stack of one ─── */
|
|
144
|
+
const model = useMemo(() => {
|
|
145
|
+
if (!value) {
|
|
146
|
+
return NO_LAYERS;
|
|
147
|
+
}
|
|
148
|
+
const source = parseIconHtml(value);
|
|
149
|
+
if (source) {
|
|
150
|
+
return source.layers;
|
|
151
|
+
}
|
|
152
|
+
const entry = value.includes('<') ? undefined : entryOf(value);
|
|
153
|
+
return entry ? [{ id: '1', icon: entry.name }] : NO_LAYERS;
|
|
154
|
+
}, [value, entryOf]);
|
|
155
|
+
// A stack waiting to be drawn into HTML: shown at once, emitted once drawn (below).
|
|
156
|
+
const [pending, setPending] = useState(null);
|
|
157
|
+
const shown = pending !== null && pending !== void 0 ? pending : model;
|
|
158
|
+
const base = shown[0];
|
|
159
|
+
const onChangeRef = useRef(onChange);
|
|
160
|
+
onChangeRef.current = onChange;
|
|
161
|
+
const emit = useCallback((next, baseName) => {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
setValue(next);
|
|
164
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, next, (_b = entryOf(baseName)) !== null && _b !== void 0 ? _b : null);
|
|
165
|
+
}, [setValue, entryOf]);
|
|
166
|
+
/** Sets the single value to a stack: its base's name, or the stack drawn as HTML. */
|
|
167
|
+
const commit = useCallback((layers) => {
|
|
168
|
+
if (layers.length === 0) {
|
|
169
|
+
setPending(null);
|
|
170
|
+
emit(null);
|
|
171
|
+
}
|
|
172
|
+
else if (valueFormat === 'name') {
|
|
173
|
+
emit(layers[0].icon, layers[0].icon);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
setPending(layers);
|
|
177
|
+
}
|
|
178
|
+
}, [valueFormat, emit]);
|
|
179
|
+
// The HTML is the icon as drawn: the hidden copy below renders the pending stack, and its
|
|
180
|
+
// markup — ids renamed after the drawing — is the new value, emitted before the paint.
|
|
181
|
+
useIsoLayoutEffect(() => {
|
|
182
|
+
var _a;
|
|
183
|
+
if (!pending) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const svg = (_a = serializerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('svg');
|
|
187
|
+
setPending(null);
|
|
188
|
+
if (svg) {
|
|
189
|
+
emit(scopeIconHtmlIds(svg.outerHTML), pending[0].icon);
|
|
190
|
+
}
|
|
191
|
+
}, [pending, emit]);
|
|
192
|
+
const selectedValues = useMemo(() => (multiSelect ? values : base ? [base.icon] : NO_NAMES), [multiSelect, values, base]);
|
|
94
193
|
/* ─── Favorites helpers ─── */
|
|
95
|
-
const addFavorite = useCallback((
|
|
194
|
+
const addFavorite = useCallback((favorite) => {
|
|
96
195
|
setFavorites((prev) => {
|
|
97
|
-
if (prev.includes(
|
|
196
|
+
if (prev.includes(favorite)) {
|
|
98
197
|
return prev;
|
|
99
198
|
}
|
|
100
|
-
const next = [...prev,
|
|
199
|
+
const next = [...prev, favorite];
|
|
101
200
|
saveToStorage(favoritesKey, next);
|
|
102
201
|
return next;
|
|
103
202
|
});
|
|
104
203
|
}, [favoritesKey]);
|
|
105
|
-
const removeFavorite = useCallback((
|
|
204
|
+
const removeFavorite = useCallback((favorite) => {
|
|
106
205
|
setFavorites((prev) => {
|
|
107
|
-
const next = prev.filter((n) => n !==
|
|
206
|
+
const next = prev.filter((n) => n !== favorite);
|
|
108
207
|
saveToStorage(favoritesKey, next);
|
|
109
208
|
return next;
|
|
110
209
|
});
|
|
111
210
|
}, [favoritesKey]);
|
|
112
|
-
const toggleFavorite = useCallback((
|
|
113
|
-
if (favorites.includes(
|
|
114
|
-
removeFavorite(
|
|
211
|
+
const toggleFavorite = useCallback((favorite) => {
|
|
212
|
+
if (favorites.includes(favorite)) {
|
|
213
|
+
removeFavorite(favorite);
|
|
115
214
|
}
|
|
116
215
|
else {
|
|
117
|
-
addFavorite(
|
|
216
|
+
addFavorite(favorite);
|
|
118
217
|
}
|
|
119
218
|
}, [favorites, addFavorite, removeFavorite]);
|
|
120
219
|
const clearFavorites = useCallback(() => {
|
|
@@ -122,10 +221,10 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
122
221
|
saveToStorage(favoritesKey, []);
|
|
123
222
|
}, [favoritesKey]);
|
|
124
223
|
/* ─── History helpers ─── */
|
|
125
|
-
const addToHistory = useCallback((
|
|
224
|
+
const addToHistory = useCallback((used) => {
|
|
126
225
|
setHistory((prev) => {
|
|
127
|
-
const filtered = prev.filter((n) => n !==
|
|
128
|
-
const next = [
|
|
226
|
+
const filtered = prev.filter((n) => n !== used);
|
|
227
|
+
const next = [used, ...filtered].slice(0, MAX_HISTORY_ITEMS);
|
|
129
228
|
saveToStorage(historyKey, next);
|
|
130
229
|
return next;
|
|
131
230
|
});
|
|
@@ -162,12 +261,14 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
162
261
|
}
|
|
163
262
|
return result;
|
|
164
263
|
}, [icons, categoriesProp]);
|
|
165
|
-
/* ─── Filter icons ─── */
|
|
264
|
+
/* ─── Filter icons: by the name shown, the export name or the category ─── */
|
|
166
265
|
const filtered = useMemo(() => {
|
|
167
266
|
let items = icons;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
items = items.filter((i) => i.
|
|
267
|
+
const q = search.trim().toLowerCase();
|
|
268
|
+
if (q) {
|
|
269
|
+
items = items.filter((i) => entryLabel(i).toLowerCase().includes(q) ||
|
|
270
|
+
i.name.toLowerCase().includes(q) ||
|
|
271
|
+
i.category.toLowerCase().includes(q));
|
|
171
272
|
}
|
|
172
273
|
if (activeCategory === '__favorites__') {
|
|
173
274
|
items = items.filter((i) => favorites.includes(i.name));
|
|
@@ -205,13 +306,8 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
205
306
|
}
|
|
206
307
|
return map;
|
|
207
308
|
}, [filtered, virtualized, virtualizedFiltered]);
|
|
208
|
-
/* ─── Selected entries ─── */
|
|
209
|
-
const selectedEntries = useMemo(() =>
|
|
210
|
-
return selectedValues
|
|
211
|
-
.map((v) => icons.find((i) => i.name === v))
|
|
212
|
-
.filter((e) => e !== undefined);
|
|
213
|
-
}, [icons, selectedValues]);
|
|
214
|
-
const selectedEntry = (_a = selectedEntries[0]) !== null && _a !== void 0 ? _a : null;
|
|
309
|
+
/* ─── Selected entries (multi-select) ─── */
|
|
310
|
+
const selectedEntries = useMemo(() => values.map((v) => entryOf(v)).filter((e) => e !== undefined), [values, entryOf]);
|
|
215
311
|
/* ─── Click outside ─── */
|
|
216
312
|
useEffect(() => {
|
|
217
313
|
if (!isOpen) {
|
|
@@ -230,6 +326,8 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
230
326
|
if (!isOpen) {
|
|
231
327
|
setSearch('');
|
|
232
328
|
setActiveCategory(null);
|
|
329
|
+
setView('icons');
|
|
330
|
+
setPicking(null);
|
|
233
331
|
setVisibleRange({ start: 0, end: virtualizedItemCount });
|
|
234
332
|
}
|
|
235
333
|
}, [isOpen, virtualizedItemCount]);
|
|
@@ -237,14 +335,27 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
237
335
|
// The panel is a dialog on the overlay stack: focus on the search field when it opens, Escape
|
|
238
336
|
// / B close only it (a document listener before) and focus returns to the trigger. The icon
|
|
239
337
|
// grid is an APG listbox moving in two dimensions — the cells were reachable by Tab only.
|
|
338
|
+
// While the grid picks an icon to stack, Escape goes back to the stack instead (1.3.9).
|
|
240
339
|
const dropdownRef = useRef(null);
|
|
241
340
|
useOverlayFocus(dropdownRef, {
|
|
242
341
|
open: isOpen,
|
|
243
342
|
modal: false,
|
|
244
343
|
initialFocus: searchInputRef,
|
|
245
|
-
onClose: () => setIsOpen(false),
|
|
344
|
+
onClose: () => (picking ? setPicking(null) : setIsOpen(false)),
|
|
246
345
|
});
|
|
247
346
|
useRovingFocus(gridWrapRef, { orientation: 'grid', itemSelector: '.ntd-picker-cell' });
|
|
347
|
+
// into the grid to pick an icon to stack: focus the search; back: the layer it went into
|
|
348
|
+
const wasPicking = useRef(false);
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
var _a, _b, _c;
|
|
351
|
+
if (picking) {
|
|
352
|
+
(_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
353
|
+
}
|
|
354
|
+
else if (wasPicking.current) {
|
|
355
|
+
(_c = (_b = dropdownRef.current) === null || _b === void 0 ? void 0 : _b.querySelector('.nice-icon-stacker__layer--selected .nice-icon-stacker__layer-select')) === null || _c === void 0 ? void 0 : _c.focus();
|
|
356
|
+
}
|
|
357
|
+
wasPicking.current = !!picking;
|
|
358
|
+
}, [picking]);
|
|
248
359
|
/* ─── Virtualization scroll handler ─── */
|
|
249
360
|
useEffect(() => {
|
|
250
361
|
if (!virtualized || !isOpen || !gridWrapRef.current) {
|
|
@@ -272,63 +383,83 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
272
383
|
}, [virtualized, isOpen, filtered.length]);
|
|
273
384
|
/* ─── Handlers ─── */
|
|
274
385
|
const handleSelect = useCallback((entry) => {
|
|
386
|
+
var _a;
|
|
387
|
+
// an icon to stack: into the layer the stacker has selected, then back to the stack
|
|
388
|
+
if (picking) {
|
|
389
|
+
(_a = stackerRef.current) === null || _a === void 0 ? void 0 : _a.add({ icon: entry.name });
|
|
390
|
+
setPicking(null);
|
|
391
|
+
setSearch('');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
275
394
|
// Add to history
|
|
276
395
|
if (showHistory) {
|
|
277
396
|
addToHistory(entry.name);
|
|
278
397
|
}
|
|
279
398
|
if (multiSelect) {
|
|
280
|
-
const isSelected =
|
|
399
|
+
const isSelected = values.includes(entry.name);
|
|
281
400
|
let newValues;
|
|
282
401
|
if (isSelected) {
|
|
283
|
-
newValues =
|
|
402
|
+
newValues = values.filter((v) => v !== entry.name);
|
|
284
403
|
}
|
|
285
404
|
else {
|
|
286
|
-
if (maxSelections &&
|
|
405
|
+
if (maxSelections && values.length >= maxSelections) {
|
|
287
406
|
return; // Max selections reached
|
|
288
407
|
}
|
|
289
|
-
newValues = [...
|
|
408
|
+
newValues = [...values, entry.name];
|
|
290
409
|
}
|
|
410
|
+
setValues(newValues);
|
|
291
411
|
if (onChangeMulti) {
|
|
292
412
|
const entries = newValues
|
|
293
|
-
.map((v) =>
|
|
413
|
+
.map((v) => entryOf(v))
|
|
294
414
|
.filter((e) => e !== undefined);
|
|
295
415
|
onChangeMulti(newValues, entries);
|
|
296
416
|
}
|
|
297
|
-
setInternalValues(newValues);
|
|
298
417
|
// Don't close in multi-select mode
|
|
299
418
|
}
|
|
300
419
|
else {
|
|
301
|
-
|
|
420
|
+
// the HTML records the variant it was drawn in; swapping the icon keeps its stack
|
|
421
|
+
const own = valueFormat === 'html' ? { variant } : {};
|
|
422
|
+
commit(canStack && base
|
|
423
|
+
? [{ ...base, icon: entry.name, ...own }, ...shown.slice(1)]
|
|
424
|
+
: [{ id: '1', icon: entry.name, ...own }]);
|
|
302
425
|
setIsOpen(false);
|
|
303
426
|
}
|
|
304
427
|
}, [
|
|
428
|
+
picking,
|
|
429
|
+
variant,
|
|
305
430
|
multiSelect,
|
|
306
|
-
|
|
431
|
+
values,
|
|
432
|
+
setValues,
|
|
307
433
|
maxSelections,
|
|
308
|
-
|
|
309
|
-
onChange,
|
|
434
|
+
entryOf,
|
|
310
435
|
onChangeMulti,
|
|
311
436
|
showHistory,
|
|
312
437
|
addToHistory,
|
|
438
|
+
valueFormat,
|
|
439
|
+
canStack,
|
|
440
|
+
base,
|
|
441
|
+
shown,
|
|
442
|
+
commit,
|
|
313
443
|
]);
|
|
314
444
|
const handleClear = useCallback((e) => {
|
|
315
445
|
e.stopPropagation();
|
|
316
446
|
if (multiSelect) {
|
|
447
|
+
setValues(NO_NAMES);
|
|
317
448
|
onChangeMulti === null || onChangeMulti === void 0 ? void 0 : onChangeMulti([], []);
|
|
318
|
-
setInternalValues([]);
|
|
319
449
|
}
|
|
320
450
|
else {
|
|
321
|
-
|
|
451
|
+
commit(NO_LAYERS);
|
|
452
|
+
setView('icons');
|
|
322
453
|
}
|
|
323
|
-
}, [multiSelect,
|
|
454
|
+
}, [multiSelect, setValues, onChangeMulti, commit]);
|
|
324
455
|
const handleToggle = useCallback(() => {
|
|
325
456
|
if (!disabled) {
|
|
326
457
|
setIsOpen((o) => !o);
|
|
327
458
|
}
|
|
328
459
|
}, [disabled]);
|
|
329
|
-
const handleFavoriteClick = useCallback((e,
|
|
460
|
+
const handleFavoriteClick = useCallback((e, favorite) => {
|
|
330
461
|
e.stopPropagation();
|
|
331
|
-
toggleFavorite(
|
|
462
|
+
toggleFavorite(favorite);
|
|
332
463
|
}, [toggleFavorite]);
|
|
333
464
|
/* ─── Drag & Drop handlers ─── */
|
|
334
465
|
const handleDragStart = useCallback((entry, e) => {
|
|
@@ -336,8 +467,7 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
336
467
|
return;
|
|
337
468
|
}
|
|
338
469
|
// Set various data formats for maximum compatibility
|
|
339
|
-
const
|
|
340
|
-
const readName = readableName(name);
|
|
470
|
+
const readName = entryLabel(entry);
|
|
341
471
|
// Plain text - icon name
|
|
342
472
|
e.dataTransfer.setData('text/plain', readName);
|
|
343
473
|
// JSON data with full icon info
|
|
@@ -349,14 +479,9 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
349
479
|
};
|
|
350
480
|
e.dataTransfer.setData('application/json', JSON.stringify(iconData));
|
|
351
481
|
e.dataTransfer.setData('text/x-ntd-icon', JSON.stringify(iconData));
|
|
352
|
-
//
|
|
482
|
+
// The cell's own drawing as an image
|
|
353
483
|
try {
|
|
354
|
-
const
|
|
355
|
-
tempContainer.style.position = 'absolute';
|
|
356
|
-
tempContainer.style.left = '-9999px';
|
|
357
|
-
document.body.appendChild(tempContainer);
|
|
358
|
-
// Use dynamic import workaround - render to string
|
|
359
|
-
const svgElement = document.querySelector(`[data-name="${readName}"] svg`);
|
|
484
|
+
const svgElement = e.currentTarget.querySelector('svg');
|
|
360
485
|
if (svgElement) {
|
|
361
486
|
const svgClone = svgElement.cloneNode(true);
|
|
362
487
|
svgClone.setAttribute('width', '64');
|
|
@@ -369,7 +494,6 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
369
494
|
img.src = URL.createObjectURL(svgBlob);
|
|
370
495
|
e.dataTransfer.setDragImage(img, 32, 32);
|
|
371
496
|
}
|
|
372
|
-
document.body.removeChild(tempContainer);
|
|
373
497
|
}
|
|
374
498
|
catch (_a) {
|
|
375
499
|
// Fallback - use default drag image
|
|
@@ -380,33 +504,106 @@ const NiceIconPicker = /* @__PURE__ */ forwardRef(function NiceIconPicker({ icon
|
|
|
380
504
|
const handleDragEnd = useCallback((entry, e) => {
|
|
381
505
|
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(entry, e);
|
|
382
506
|
}, [onDragEnd]);
|
|
383
|
-
const readableName = (name) => name.replace(/^Ntd/, '');
|
|
384
507
|
const triggerLabel = useMemo(() => {
|
|
385
|
-
if (multiSelect
|
|
508
|
+
if (multiSelect) {
|
|
386
509
|
if (selectedEntries.length === 1) {
|
|
387
|
-
return
|
|
510
|
+
return entryLabel(selectedEntries[0]);
|
|
388
511
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
512
|
+
if (selectedEntries.length > 1) {
|
|
513
|
+
return t('iconPicker.iconsSelected', '{count} icons selected').replace('{count}', String(selectedEntries.length));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else if (base) {
|
|
517
|
+
return labelOf(base.icon);
|
|
518
|
+
}
|
|
519
|
+
return placeholder !== null && placeholder !== void 0 ? placeholder : t('iconPicker.selectIcon', 'Select icon…');
|
|
520
|
+
}, [multiSelect, selectedEntries, base, labelOf, placeholder, t]);
|
|
521
|
+
const hasSelection = multiSelect ? selectedEntries.length > 0 : !!base;
|
|
522
|
+
const iconOnly = displayMode === 'icon';
|
|
523
|
+
// the field's label first, then what is chosen — the icon-only trigger has no text of its own
|
|
524
|
+
const triggerName = ariaLabel
|
|
525
|
+
? `${ariaLabel}: ${triggerLabel}`
|
|
526
|
+
: iconOnly
|
|
527
|
+
? triggerLabel
|
|
528
|
+
: undefined;
|
|
529
|
+
const overlays = Math.max(0, shown.length - 1);
|
|
530
|
+
const showGrid = view === 'icons' || !!picking;
|
|
531
|
+
// Where the panel opens (1.3.9): above the trigger when there is more room there, against its
|
|
532
|
+
// right edge near the window's, never taller than the room it has — the stack view is tall.
|
|
533
|
+
// The room is what the window and every clipping or scrolling ancestor leave visible.
|
|
534
|
+
const [placement, setPlacement] = useState({
|
|
535
|
+
up: false,
|
|
536
|
+
end: false,
|
|
537
|
+
});
|
|
538
|
+
useIsoLayoutEffect(() => {
|
|
539
|
+
const root = rootRef.current;
|
|
540
|
+
if (!isOpen || !root || typeof window === 'undefined') {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const r = root.getBoundingClientRect();
|
|
544
|
+
const bounds = visibleBounds(root);
|
|
545
|
+
const want = showGrid ? 460 : 620;
|
|
546
|
+
const below = bounds.bottom - r.bottom - 12;
|
|
547
|
+
const above = r.top - bounds.top - 12;
|
|
548
|
+
const up = below < want && above > below;
|
|
549
|
+
const room = up ? above : below;
|
|
550
|
+
setPlacement({
|
|
551
|
+
up,
|
|
552
|
+
end: r.left + 380 > bounds.right - 8 && r.right - bounds.left >= 380,
|
|
553
|
+
maxHeight: room < want ? Math.max(240, Math.floor(room)) : undefined,
|
|
554
|
+
});
|
|
555
|
+
}, [isOpen, showGrid]);
|
|
556
|
+
const pickingInto = picking ? shown.find((l) => l.id === picking.target) : undefined;
|
|
557
|
+
const SingleMulti = (_a = selectedEntries[0]) === null || _a === void 0 ? void 0 : _a.Component;
|
|
558
|
+
return (jsxs("div", { ref: rootRef, id: id, "data-testid": dataTestId !== null && dataTestId !== void 0 ? dataTestId : testId, className: `ntd-picker${isOpen ? ' ntd-picker--open' : ''}${disabled ? ' ntd-picker--disabled' : ''}${multiSelect ? ' ntd-picker--multi' : ''}${iconOnly ? ' ntd-picker--icon-only' : ''}${className ? ` ${className}` : ''}`, children: [jsxs("button", { type: "button", className: "ntd-picker-trigger", onClick: handleToggle, disabled: disabled, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-label": triggerName, title: iconOnly ? triggerLabel : undefined, children: [jsx("span", { className: "ntd-picker-trigger-icon", children: !multiSelect && base ? (renderIconStack(shown, resolve, {
|
|
559
|
+
baseProps: { ...drawProps, size: triggerIconSize },
|
|
560
|
+
})) : multiSelect && selectedEntries.length > 1 ? (jsx("span", { className: "ntd-picker-multi-badge", children: selectedEntries.length })) : SingleMulti ? (jsx(SingleMulti, { ...drawProps, size: triggerIconSize })) : (jsx(NtdGrid, { size: 16, variant: "outline" })) }), !iconOnly && (jsxs(Fragment, { children: [jsx("span", { className: `ntd-picker-trigger-label${hasSelection ? '' : ' ntd-picker-trigger-placeholder'}`, children: triggerLabel }), jsx("span", { className: "ntd-picker-trigger-chevron", children: jsx(Chevron, {}) })] }))] }), name &&
|
|
561
|
+
(multiSelect ? (values.map((v) => jsx("input", { type: "hidden", name: name, value: v }, v))) : (jsx("input", { type: "hidden", name: name, value: value !== null && value !== void 0 ? value : '' }))), pending && (jsx("span", { ref: serializerRef, className: "ntd-picker-serializer", hidden: true, children: renderIconStack(pending, resolve, {
|
|
562
|
+
baseProps: {
|
|
563
|
+
...drawProps,
|
|
564
|
+
width: '1em',
|
|
565
|
+
height: '1em',
|
|
566
|
+
title: labelOf(pending[0].icon),
|
|
567
|
+
...iconHtmlAttributes(pending),
|
|
568
|
+
},
|
|
569
|
+
}) })), isOpen && (jsxs("div", { ref: dropdownRef, className: `ntd-picker-dropdown${showGrid ? '' : ' ntd-picker-dropdown--stack'}${placement.up ? ' ntd-picker-dropdown--up' : ''}${placement.end ? ' ntd-picker-dropdown--end' : ''}`, style: placement.maxHeight ? { maxHeight: placement.maxHeight } : undefined, role: "dialog", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [canStack &&
|
|
570
|
+
(picking ? (jsxs("div", { className: "ntd-picker-banner", children: [jsx("button", { type: "button", className: "ntd-picker-back", onClick: () => setPicking(null), "aria-label": t('iconPicker.back', 'Back'), title: t('iconPicker.back', 'Back'), children: jsx(NtdArrowLeft, { size: 14, variant: "outline" }) }), jsx("span", { children: t('iconPicker.pickOverlay', 'Pick an icon to put on {icon}').replace('{icon}', pickingInto ? labelOf(pickingInto.icon) : base ? labelOf(base.icon) : '') })] })) : (jsxs("div", { className: "ntd-picker-views", ref: viewsRef, role: "tablist", "aria-label": t('iconPicker.selectIcon', 'Select icon…'), children: [jsx("button", { type: "button", role: "tab", id: `${panelId}-icons`, "aria-selected": view === 'icons', "aria-controls": `${panelId}-panel`, className: `ntd-picker-view${view === 'icons' ? ' ntd-picker-view--active' : ''}`, onClick: () => setView('icons'), children: t('iconPicker.tabIcon', 'Icon') }), jsxs("button", { type: "button", role: "tab", id: `${panelId}-stack`, "aria-selected": view === 'stack', "aria-controls": `${panelId}-panel`, className: `ntd-picker-view${view === 'stack' ? ' ntd-picker-view--active' : ''}`, onClick: () => setView('stack'), disabled: !base, title: base ? undefined : t('iconPicker.stackNeedsIcon', 'Pick an icon first'), children: [t('iconPicker.tabStack', 'Overlays'), overlays > 0 && jsx("span", { className: "ntd-picker-tab-count", children: overlays })] })] }))), jsxs("div", { className: "ntd-picker-panel", id: `${panelId}-panel`, ...(canStack && !picking
|
|
571
|
+
? {
|
|
572
|
+
role: 'tabpanel',
|
|
573
|
+
'aria-labelledby': `${panelId}-${view}`,
|
|
574
|
+
}
|
|
575
|
+
: {}), children: [showGrid && (jsxs(Fragment, { children: [jsxs("div", { className: "ntd-picker-search", children: [jsx("span", { className: "ntd-picker-search-icon", children: jsx(SearchIcon, {}) }), jsx("input", { ref: searchInputRef, type: "text", placeholder: t('iconPicker.searchIcons', 'Search icons…'), value: search, onChange: (e) => setSearch(e.target.value), "aria-label": t('iconPicker.searchIcons2', 'Search icons') })] }), (allCategories.length > 1 || showFavorites || showHistory) && (jsxs("div", { className: "ntd-picker-tabs", ref: tablistRef, role: "tablist", children: [jsx("button", { type: "button", className: `ntd-picker-tab${activeCategory === null ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory(null), role: "tab", "aria-selected": activeCategory === null, children: t('iconPicker.all', 'All') }), showFavorites && (jsxs("button", { type: "button", className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === '__favorites__' ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory('__favorites__'), role: "tab", "aria-selected": activeCategory === '__favorites__', title: t('iconPicker.favorites', 'Favorites'), children: [jsx(StarIcon, { filled: true }), " ", t('iconPicker.favorites', 'Favorites'), favorites.length > 0 && (jsx("span", { className: "ntd-picker-tab-count", children: favorites.length }))] })), showHistory && history.length > 0 && (jsxs("button", { type: "button", className: `ntd-picker-tab ntd-picker-tab-special${activeCategory === '__history__' ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory('__history__'), role: "tab", "aria-selected": activeCategory === '__history__', title: t('iconPicker.recentlyUsed', 'Recently used'), children: [jsx(ClockIcon, {}), " ", t('iconPicker.recent', 'Recent')] })), allCategories.map((cat) => (jsx("button", { type: "button", className: `ntd-picker-tab${activeCategory === cat ? ' ntd-picker-tab--active' : ''}`, onClick: () => setActiveCategory(cat), role: "tab", "aria-selected": activeCategory === cat, children: cat }, cat)))] })), jsx("div", { className: "ntd-picker-grid-wrap", ref: gridWrapRef, role: "listbox", "aria-label": t('iconPicker.icons', 'Icons'), "aria-multiselectable": multiSelect || undefined, children: filtered.length === 0 ? (jsxs("div", { className: "ntd-picker-empty", children: [jsx("div", { className: "ntd-picker-empty-icon", children: jsx(NtdInbox, { size: 28, variant: "outline" }) }), activeCategory === '__favorites__'
|
|
576
|
+
? t('iconPicker.noFavoritesYetClickThe', 'No favorites yet. Click the star icon to add.')
|
|
577
|
+
: activeCategory === '__history__'
|
|
578
|
+
? t('iconPicker.noRecentIcons', 'No recent icons.')
|
|
579
|
+
: t('iconPicker.noIconsFound', 'No icons found')] })) : (jsxs(Fragment, { children: [virtualized && visibleRange.start > 0 && (jsx("div", { style: { height: Math.floor(visibleRange.start / 6) * 48 } })), Array.from(grouped.entries()).map(([category, entries]) => (jsxs("div", { children: [!activeCategory && (jsx("div", { className: "ntd-picker-category-label", children: category })), jsx("div", { className: `ntd-picker-grid${optionDisplayMode === 'icon-name' ? ' ntd-picker-grid--names' : ''}`, children: entries.map((entry) => {
|
|
580
|
+
const isSelected = !picking && selectedValues.includes(entry.name);
|
|
581
|
+
const isFavorite = favorites.includes(entry.name);
|
|
582
|
+
const label = entryLabel(entry);
|
|
583
|
+
const cell = (jsxs("button", { type: "button", className: `ntd-picker-cell${isSelected ? ' ntd-picker-cell--selected' : ''}${multiSelect ? ' ntd-picker-cell--multi-select' : ''}${enableDragAndDrop ? ' ntd-picker-cell--draggable' : ''}`, onClick: () => handleSelect(entry), "data-name": label, role: "option", "aria-selected": isSelected, "aria-label": label, title: label, draggable: enableDragAndDrop, onDragStart: enableDragAndDrop
|
|
584
|
+
? (e) => handleDragStart(entry, e)
|
|
585
|
+
: undefined, onDragEnd: enableDragAndDrop ? (e) => handleDragEnd(entry, e) : undefined, children: [jsx(entry.Component, { ...drawProps, size: gridIconSize }), optionDisplayMode === 'icon-name' && (jsx("span", { className: "ntd-picker-cell-name", children: label })), multiSelect && isSelected && (jsx("span", { className: "ntd-picker-cell-check", children: jsx(CheckIcon, {}) }))] }, entry.name));
|
|
586
|
+
if (!showFavorites) {
|
|
587
|
+
return cell;
|
|
588
|
+
}
|
|
589
|
+
// the star beside the cell, not in it — a button in a button is
|
|
590
|
+
// invalid HTML (1.3.9)
|
|
591
|
+
const favLabel = isFavorite
|
|
592
|
+
? t('iconPicker.removeFromFavorites', 'Remove from favorites')
|
|
593
|
+
: t('iconPicker.addToFavorites', 'Add to favorites');
|
|
594
|
+
return (jsxs("div", { className: "ntd-picker-cell-wrap", children: [cell, jsx("button", { type: "button", tabIndex: -1, className: `ntd-picker-cell-fav${isFavorite ? ' ntd-picker-cell-fav--active' : ''}`, onClick: (e) => handleFavoriteClick(e, entry.name), "aria-label": `${favLabel}: ${label}`, title: favLabel, children: jsx(StarIcon, { filled: isFavorite }) })] }, entry.name));
|
|
595
|
+
}) })] }, category))), virtualized && visibleRange.end < filtered.length && (jsx("div", { style: {
|
|
596
|
+
height: Math.ceil((filtered.length - visibleRange.end) / 6) * 48,
|
|
597
|
+
} }))] })) })] })), canStack && view === 'stack' && base && (jsx("div", { className: "ntd-picker-stack", hidden: !!picking, children: jsx(NiceIconStacker, { ref: stackerRef, icons: resolve, layers: shown, onLayersChange: commit, onAddClick: (target) => {
|
|
598
|
+
var _a;
|
|
599
|
+
setSearch('');
|
|
600
|
+
setActiveCategory(null);
|
|
601
|
+
setPicking({ target: (_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : base.id });
|
|
602
|
+
}, previewProps: drawProps, iconLabel: labelOf, showCode: false, keepBase: true }) }))] }), !picking && (jsxs("div", { className: "ntd-picker-footer", children: [jsx("span", { className: "ntd-picker-count", children: showGrid && (jsxs(Fragment, { children: [(filtered.length === 1
|
|
603
|
+
? t('iconPicker.iconCountOne', '{count} icon')
|
|
604
|
+
: t('iconPicker.iconCountMany', '{count} icons')).replace('{count}', String(filtered.length)), multiSelect && selectedValues.length > 0 && (jsxs(Fragment, { children: [' ', t('iconPicker.selected', '• {selectedValues} selected').replace('{selectedValues}', String(selectedValues.length))] })), maxSelections && multiSelect && (jsxs(Fragment, { children: [' ', t('iconPicker.max', '(max {maxSelections})').replace('{maxSelections}', String(maxSelections))] }))] })) }), clearable && hasSelection && (jsx("button", { type: "button", className: "ntd-picker-clear", onClick: handleClear, children: multiSelect
|
|
408
605
|
? t('iconPicker.clearAll', 'Clear all')
|
|
409
|
-
: t('iconPicker.clearSelection', 'Clear selection') })), multiSelect && (jsx("button", { type: "button", className: "ntd-picker-done", onClick: () => setIsOpen(false), children: t('iconPicker.done', 'Done') }))] })] }))] }));
|
|
606
|
+
: t('iconPicker.clearSelection', 'Clear selection') })), (multiSelect || view === 'stack') && (jsx("button", { type: "button", className: "ntd-picker-done", onClick: () => setIsOpen(false), children: t('iconPicker.done', 'Done') }))] }))] }))] }));
|
|
410
607
|
});
|
|
411
608
|
|
|
412
609
|
export { NiceIconPicker };
|