@assembly-js/design-system 3.1.2 → 3.1.3

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 (69) hide show
  1. package/dist/esm/components/Icon.js +38 -0
  2. package/dist/esm/components/IconPicker/IconGrid.js +201 -0
  3. package/dist/esm/components/IconPicker/IconPicker.js +254 -0
  4. package/dist/esm/components/IconPicker/__tests__/searchIcons.test.js +55 -0
  5. package/dist/esm/components/IconPicker/dsPickerIcons.js +18 -0
  6. package/dist/esm/components/IconPicker/icons.generated.js +3 -0
  7. package/dist/esm/components/IconPicker/index.js +3 -0
  8. package/dist/esm/components/IconPicker/pickerIcons.js +20 -0
  9. package/dist/esm/components/IconPicker/searchIcons.js +122 -0
  10. package/dist/esm/icons/iconToFaMap.js +309 -0
  11. package/dist/esm/index.js +1 -0
  12. package/dist/esm/types/components/Avatar/Avatar.d.ts +4 -4
  13. package/dist/esm/types/components/Icon.d.ts +10 -1
  14. package/dist/esm/types/components/IconPicker/IconGrid.d.ts +13 -0
  15. package/dist/esm/types/components/IconPicker/IconPicker.d.ts +15 -0
  16. package/dist/esm/types/components/IconPicker/IconPicker.stories.d.ts +8 -0
  17. package/dist/esm/types/components/IconPicker/__tests__/searchIcons.test.d.ts +1 -0
  18. package/dist/esm/types/components/IconPicker/dsPickerIcons.d.ts +7 -0
  19. package/dist/esm/types/components/IconPicker/icons.generated.d.ts +9 -0
  20. package/dist/esm/types/components/IconPicker/index.d.ts +3 -0
  21. package/dist/esm/types/components/IconPicker/pickerIcons.d.ts +8 -0
  22. package/dist/esm/types/components/IconPicker/searchIcons.d.ts +2 -0
  23. package/dist/esm/types/components/Toast/Toast.d.ts +1 -1
  24. package/dist/esm/types/icons/iconToFaMap.d.ts +8 -0
  25. package/dist/esm/types/index.d.ts +1 -0
  26. package/dist/esm/types/tsconfig.tsbuildinfo +1 -1
  27. package/dist/styles/main.css +1 -1
  28. package/dist/types/components/Avatar/Avatar.d.ts +4 -4
  29. package/dist/types/components/Icon.d.ts +10 -1
  30. package/dist/types/components/IconPicker/IconGrid.d.ts +13 -0
  31. package/dist/types/components/IconPicker/IconPicker.d.ts +15 -0
  32. package/dist/types/components/IconPicker/IconPicker.stories.d.ts +8 -0
  33. package/dist/types/components/IconPicker/__tests__/searchIcons.test.d.ts +1 -0
  34. package/dist/types/components/IconPicker/dsPickerIcons.d.ts +7 -0
  35. package/dist/types/components/IconPicker/icons.generated.d.ts +9 -0
  36. package/dist/types/components/IconPicker/index.d.ts +3 -0
  37. package/dist/types/components/IconPicker/pickerIcons.d.ts +8 -0
  38. package/dist/types/components/IconPicker/searchIcons.d.ts +2 -0
  39. package/dist/types/components/Toast/Toast.d.ts +1 -1
  40. package/dist/types/icons/iconToFaMap.d.ts +8 -0
  41. package/dist/types/index.d.ts +1 -0
  42. package/dist/types/tsconfig.tsbuildinfo +1 -1
  43. package/dist/umd/components/Icon.js +40 -4
  44. package/dist/umd/components/IconPicker/IconGrid.js +222 -0
  45. package/dist/umd/components/IconPicker/IconPicker.js +269 -0
  46. package/dist/umd/components/IconPicker/__tests__/searchIcons.test.js +70 -0
  47. package/dist/umd/components/IconPicker/dsPickerIcons.js +41 -0
  48. package/dist/umd/components/IconPicker/icons.generated.js +3 -0
  49. package/dist/umd/components/IconPicker/index.js +28 -0
  50. package/dist/umd/components/IconPicker/pickerIcons.js +38 -0
  51. package/dist/umd/components/IconPicker/searchIcons.js +141 -0
  52. package/dist/umd/icons/iconToFaMap.js +329 -0
  53. package/dist/umd/index.js +14 -4
  54. package/dist/umd/types/components/Avatar/Avatar.d.ts +4 -4
  55. package/dist/umd/types/components/Icon.d.ts +10 -1
  56. package/dist/umd/types/components/IconPicker/IconGrid.d.ts +13 -0
  57. package/dist/umd/types/components/IconPicker/IconPicker.d.ts +15 -0
  58. package/dist/umd/types/components/IconPicker/IconPicker.stories.d.ts +8 -0
  59. package/dist/umd/types/components/IconPicker/__tests__/searchIcons.test.d.ts +1 -0
  60. package/dist/umd/types/components/IconPicker/dsPickerIcons.d.ts +7 -0
  61. package/dist/umd/types/components/IconPicker/icons.generated.d.ts +9 -0
  62. package/dist/umd/types/components/IconPicker/index.d.ts +3 -0
  63. package/dist/umd/types/components/IconPicker/pickerIcons.d.ts +8 -0
  64. package/dist/umd/types/components/IconPicker/searchIcons.d.ts +2 -0
  65. package/dist/umd/types/components/Toast/Toast.d.ts +1 -1
  66. package/dist/umd/types/icons/iconToFaMap.d.ts +8 -0
  67. package/dist/umd/types/index.d.ts +1 -0
  68. package/dist/umd/types/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +9 -2
@@ -1,12 +1,50 @@
1
1
  // This file is generated by a script. Do not edit this file directly.
2
2
  var _excluded = ["icon"];
3
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
4
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], t.indexOf(o) >= 0 || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
4
5
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.indexOf(n) >= 0) continue; t[n] = r[n]; } return t; }
5
6
  import React from "react";
6
7
  import * as icons from "../icons";
8
+ import { icons as faIcons } from "./IconPicker/icons.generated";
9
+ import { iconToFaMap } from "../icons/iconToFaMap";
10
+ var faIconMap;
11
+ function getFAIconMap() {
12
+ if (!faIconMap) {
13
+ faIconMap = new Map(faIcons.map(function (i) {
14
+ return [i.name, i];
15
+ }));
16
+ }
17
+ return faIconMap;
18
+ }
19
+
20
+ /**
21
+ * Icon component that renders either a Font Awesome icon or a Design System icon.
22
+ * it checks if the icon name is in the Font Awesome icon set and renders the corresponding icon.
23
+ * if not, it falls back to the Design System icon set and renders the corresponding icon.
24
+ * @param icon - The name of the icon to render.
25
+ * @param props - The props to pass to the icon component.
26
+ * @returns The icon component.
27
+ */
7
28
  export var Icon = function Icon(_ref) {
8
29
  var icon = _ref.icon,
9
30
  props = _objectWithoutProperties(_ref, _excluded);
31
+ var mappedFaIcon = iconToFaMap[icon];
32
+
33
+ // if the icon name has a mapped fa icon, render the mapped fa icon, otherwise render the fa icon
34
+ var faIcon = mappedFaIcon ? getFAIconMap().get(mappedFaIcon) : getFAIconMap().get(icon);
35
+ if (faIcon) {
36
+ return /*#__PURE__*/React.createElement("svg", _extends({
37
+ xmlns: "http://www.w3.org/2000/svg",
38
+ viewBox: faIcon.viewBox,
39
+ fill: "currentColor",
40
+ overflow: "visible",
41
+ "aria-hidden": "true"
42
+ }, props), /*#__PURE__*/React.createElement("path", {
43
+ d: faIcon.path
44
+ }));
45
+ }
46
+
47
+ // Fall back to DS icon
10
48
  var IconComponent = icons[icon];
11
49
  if (!IconComponent) return null;
12
50
  return /*#__PURE__*/React.createElement(IconComponent, props);
@@ -0,0 +1,201 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
8
+ import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef } from 'react';
9
+ import { cn } from "../../common/utils";
10
+ import { BaseButton } from "../Button/Base";
11
+ function renderIcon(icon) {
12
+ if (icon.source === 'ds') {
13
+ return /*#__PURE__*/React.createElement(icon.Component, {
14
+ className: "cop-h-[19px] cop-w-[19px]",
15
+ "aria-hidden": "true"
16
+ });
17
+ }
18
+ return /*#__PURE__*/React.createElement("svg", {
19
+ viewBox: icon.viewBox,
20
+ fill: "currentColor",
21
+ className: "cop-h-[19px] cop-w-[19px]",
22
+ overflow: "visible",
23
+ "aria-hidden": "true"
24
+ }, /*#__PURE__*/React.createElement("path", {
25
+ d: icon.path
26
+ }));
27
+ }
28
+ var COLUMNS = 8;
29
+ export var IconGrid = /*#__PURE__*/forwardRef(function (_ref, ref) {
30
+ var icons = _ref.icons,
31
+ onSelect = _ref.onSelect,
32
+ selectedIcon = _ref.selectedIcon,
33
+ onEscapeFromGrid = _ref.onEscapeFromGrid;
34
+ var _useState = useState(-1),
35
+ _useState2 = _slicedToArray(_useState, 2),
36
+ focusedIndex = _useState2[0],
37
+ setFocusedIndex = _useState2[1];
38
+ var itemRefs = useRef([]);
39
+ var hasScrolledToSelected = useRef(false);
40
+
41
+ // Expose focusFirst so the parent can transfer focus into the grid
42
+ useImperativeHandle(ref, function () {
43
+ return {
44
+ focusFirst: function focusFirst() {
45
+ var selectedIdx = icons.findIndex(function (icon) {
46
+ return icon.name === selectedIcon;
47
+ });
48
+ setFocusedIndex(selectedIdx >= 0 ? selectedIdx : 0);
49
+ },
50
+ resetFocus: function resetFocus() {
51
+ setFocusedIndex(-1);
52
+ }
53
+ };
54
+ });
55
+
56
+ // Reset focused index and trim stale refs when the icon list changes
57
+ useEffect(function () {
58
+ setFocusedIndex(-1);
59
+ itemRefs.current.length = icons.length;
60
+ }, [icons]);
61
+
62
+ // Focus the item at focusedIndex when it changes
63
+ useEffect(function () {
64
+ if (focusedIndex >= 0 && focusedIndex < icons.length) {
65
+ var el = itemRefs.current[focusedIndex];
66
+ var btn = el === null || el === void 0 ? void 0 : el.querySelector('button');
67
+ btn === null || btn === void 0 || btn.focus();
68
+ el === null || el === void 0 || el.scrollIntoView({
69
+ block: 'nearest'
70
+ });
71
+ }
72
+ }, [focusedIndex, icons.length]);
73
+ var handleKeyDown = useCallback(function (e) {
74
+ var len = icons.length;
75
+ if (len === 0) return;
76
+ var nextIndex = focusedIndex;
77
+ var col = focusedIndex % COLUMNS;
78
+ switch (e.key) {
79
+ case 'ArrowRight':
80
+ // Stop at end of row
81
+ if (col < COLUMNS - 1 && focusedIndex + 1 < len) {
82
+ nextIndex = focusedIndex + 1;
83
+ }
84
+ break;
85
+ case 'ArrowLeft':
86
+ // Stop at start of row
87
+ if (col > 0) {
88
+ nextIndex = focusedIndex - 1;
89
+ }
90
+ break;
91
+ case 'ArrowDown':
92
+ nextIndex = focusedIndex + COLUMNS;
93
+ break;
94
+ case 'ArrowUp':
95
+ // If in the top row, escape back to search
96
+ if (focusedIndex < COLUMNS) {
97
+ onEscapeFromGrid === null || onEscapeFromGrid === void 0 || onEscapeFromGrid();
98
+ setFocusedIndex(-1);
99
+ e.preventDefault();
100
+ return;
101
+ }
102
+ nextIndex = focusedIndex - COLUMNS;
103
+ break;
104
+ case 'Home':
105
+ nextIndex = 0;
106
+ break;
107
+ case 'End':
108
+ nextIndex = len - 1;
109
+ break;
110
+ case 'Enter':
111
+ {
112
+ var icon = icons[focusedIndex];
113
+ if (focusedIndex >= 0 && icon) {
114
+ onSelect(icon);
115
+ }
116
+ e.preventDefault();
117
+ return;
118
+ }
119
+ default:
120
+ return;
121
+ }
122
+ e.preventDefault();
123
+ // Clamp within bounds
124
+ nextIndex = Math.max(0, Math.min(nextIndex, len - 1));
125
+ setFocusedIndex(nextIndex);
126
+ }, [focusedIndex, icons, onSelect, onEscapeFromGrid]);
127
+
128
+ // Find the boundary between DS and FA icons (for the divider)
129
+ var dividerIndex = useMemo(function () {
130
+ var firstFaIdx = icons.findIndex(function (icon) {
131
+ return icon.source === 'fa';
132
+ });
133
+ // Show divider only when both DS and FA icons are present
134
+ if (firstFaIdx > 0 && icons.some(function (icon) {
135
+ return icon.source === 'ds';
136
+ })) {
137
+ return firstFaIdx;
138
+ }
139
+ return -1;
140
+ }, [icons]);
141
+
142
+ // Row index where the divider appears (between DS and FA sections)
143
+ var dividerRowIndex = dividerIndex > 0 ? Math.ceil(dividerIndex / COLUMNS) : -1;
144
+
145
+ // Chunk icons into rows for proper ARIA grid hierarchy
146
+ var rows = useMemo(function () {
147
+ var result = [];
148
+ for (var i = 0; i < icons.length; i += COLUMNS) {
149
+ result.push(icons.slice(i, i + COLUMNS));
150
+ }
151
+ return result;
152
+ }, [icons]);
153
+ if (icons.length === 0) {
154
+ return /*#__PURE__*/React.createElement("div", {
155
+ className: "cop-flex cop-h-[262px] cop-items-center cop-justify-center"
156
+ }, /*#__PURE__*/React.createElement("p", {
157
+ className: "cop-text-sm cop-text-[#6b6f76]"
158
+ }, "No icons found"));
159
+ }
160
+ return /*#__PURE__*/React.createElement("div", {
161
+ role: "grid",
162
+ "aria-label": "Icon grid",
163
+ onKeyDown: handleKeyDown,
164
+ className: "cop--m-[3px] cop-grid cop-h-[262px] cop-content-start cop-overflow-y-auto cop-p-[3px] [scrollbar-color:rgba(0,0,0,0.2)_transparent] [scrollbar-width:thin]"
165
+ }, rows.map(function (row, rowIndex) {
166
+ return /*#__PURE__*/React.createElement(React.Fragment, {
167
+ key: rowIndex
168
+ }, rowIndex === dividerRowIndex && /*#__PURE__*/React.createElement("div", {
169
+ role: "separator",
170
+ className: "cop-my-2 cop-h-px cop-bg-[#eff1f4]"
171
+ }), /*#__PURE__*/React.createElement("div", {
172
+ role: "row",
173
+ className: "cop-grid cop-grid-cols-[repeat(8,1fr)] cop-gap-x-[6px] cop-gap-y-[8px]"
174
+ }, row.map(function (icon, colIndex) {
175
+ var flatIndex = rowIndex * COLUMNS + colIndex;
176
+ var isSelected = selectedIcon === icon.name;
177
+ return /*#__PURE__*/React.createElement("div", {
178
+ key: icon.name,
179
+ role: "gridcell",
180
+ ref: function ref(el) {
181
+ itemRefs.current[flatIndex] = el;
182
+ if (isSelected && el && !hasScrolledToSelected.current) {
183
+ hasScrolledToSelected.current = true;
184
+ el.scrollIntoView({
185
+ block: 'nearest'
186
+ });
187
+ }
188
+ }
189
+ }, /*#__PURE__*/React.createElement(BaseButton, {
190
+ variant: "minimal",
191
+ tabIndex: flatIndex === focusedIndex ? 0 : -1,
192
+ onClick: function onClick() {
193
+ return onSelect(icon);
194
+ },
195
+ "aria-label": icon.name,
196
+ className: cn('cop-p-[5px]', isSelected && 'cop-bg-secondary cop-text-gray-700 hover:cop-border-secondary hover:cop-bg-secondary')
197
+ }, renderIcon(icon)));
198
+ })));
199
+ }));
200
+ });
201
+ IconGrid.displayName = 'IconGrid';
@@ -0,0 +1,254 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ 'use client';
3
+
4
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
5
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
7
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
8
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
9
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
10
+ import * as React from 'react';
11
+ import ReactDOM from 'react-dom';
12
+ import { cn } from "../../common/utils";
13
+ import { BaseButton } from "../Button/Base";
14
+ import { Input } from "../Input";
15
+ import { IconGrid } from "./IconGrid";
16
+ import { searchIcons } from "./searchIcons";
17
+ import { pickerIcons } from "./pickerIcons";
18
+ import { iconToFaMap } from "../../icons/iconToFaMap";
19
+ var IconPicker = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
20
+ var value = _ref.value,
21
+ onChange = _ref.onChange,
22
+ defaultValue = _ref.defaultValue,
23
+ _ref$placeholder = _ref.placeholder,
24
+ placeholder = _ref$placeholder === void 0 ? 'Select an icon' : _ref$placeholder,
25
+ className = _ref.className,
26
+ _ref$disabled = _ref.disabled,
27
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled;
28
+ var isControlled = value !== undefined;
29
+ var _React$useState = React.useState(defaultValue),
30
+ _React$useState2 = _slicedToArray(_React$useState, 2),
31
+ internalValue = _React$useState2[0],
32
+ setInternalValue = _React$useState2[1];
33
+ var selectedIcon = React.useMemo(function () {
34
+ if (isControlled) {
35
+ // if the icon value is a legacy icon picker value
36
+ if (iconToFaMap[value]) {
37
+ return iconToFaMap[value];
38
+ }
39
+ return value;
40
+ } else {
41
+ return internalValue;
42
+ }
43
+ }, [isControlled, value, internalValue]);
44
+ var selectedIconData = React.useMemo(function () {
45
+ return pickerIcons.find(function (icon) {
46
+ return icon.name === selectedIcon;
47
+ });
48
+ }, [selectedIcon]);
49
+ var _React$useState3 = React.useState(false),
50
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
51
+ open = _React$useState4[0],
52
+ setOpen = _React$useState4[1];
53
+ var _React$useState5 = React.useState(''),
54
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
55
+ search = _React$useState6[0],
56
+ setSearch = _React$useState6[1];
57
+ var _React$useState7 = React.useState(''),
58
+ _React$useState8 = _slicedToArray(_React$useState7, 2),
59
+ debouncedSearch = _React$useState8[0],
60
+ setDebouncedSearch = _React$useState8[1];
61
+ var triggerRef = React.useRef(null);
62
+ var popoverRef = React.useRef(null);
63
+ var searchInputRef = React.useRef(null);
64
+ var gridRef = React.useRef(null);
65
+ var _React$useState9 = React.useState({
66
+ top: 0,
67
+ left: 0
68
+ }),
69
+ _React$useState10 = _slicedToArray(_React$useState9, 2),
70
+ coords = _React$useState10[0],
71
+ setCoords = _React$useState10[1];
72
+
73
+ // Merge forwarded ref with internal ref
74
+ React.useImperativeHandle(ref, function () {
75
+ return triggerRef.current;
76
+ });
77
+
78
+ // Debounce search input
79
+ React.useEffect(function () {
80
+ var timeout = setTimeout(function () {
81
+ return setDebouncedSearch(search);
82
+ }, 200);
83
+ return function () {
84
+ return clearTimeout(timeout);
85
+ };
86
+ }, [search]);
87
+
88
+ // Filter icons by search
89
+ var filteredIcons = React.useMemo(function () {
90
+ return searchIcons(debouncedSearch, pickerIcons);
91
+ }, [debouncedSearch]);
92
+
93
+ // Position popover below trigger with viewport boundary checks
94
+ var updatePosition = React.useCallback(function () {
95
+ var _popoverRef$current$o, _popoverRef$current;
96
+ if (!triggerRef.current) return;
97
+ var rect = triggerRef.current.getBoundingClientRect();
98
+ var popoverWidth = 330;
99
+ var popoverHeight = (_popoverRef$current$o = (_popoverRef$current = popoverRef.current) === null || _popoverRef$current === void 0 ? void 0 : _popoverRef$current.offsetHeight) !== null && _popoverRef$current$o !== void 0 ? _popoverRef$current$o : 300;
100
+ var gap = 4;
101
+ var top = rect.bottom + gap;
102
+ var left = rect.left;
103
+
104
+ // If popover overflows bottom, position above trigger
105
+ if (top + popoverHeight > window.innerHeight) {
106
+ top = rect.top - gap - popoverHeight;
107
+ }
108
+
109
+ // Clamp horizontal position within viewport
110
+ if (left + popoverWidth > window.innerWidth) {
111
+ left = window.innerWidth - popoverWidth - gap;
112
+ }
113
+ if (left < gap) {
114
+ left = gap;
115
+ }
116
+ setCoords({
117
+ top: top,
118
+ left: left
119
+ });
120
+ }, []);
121
+
122
+ // Update position when opened
123
+ React.useLayoutEffect(function () {
124
+ if (open) {
125
+ updatePosition();
126
+ // Focus search input after positioning
127
+ requestAnimationFrame(function () {
128
+ var _searchInputRef$curre;
129
+ return (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.focus();
130
+ });
131
+ }
132
+ }, [open, updatePosition]);
133
+
134
+ // Lock body scroll while open and reposition on resize
135
+ React.useEffect(function () {
136
+ if (!open) return;
137
+ var prevOverflow = document.body.style.overflow;
138
+ document.body.style.overflow = 'hidden';
139
+ window.addEventListener('resize', updatePosition);
140
+ return function () {
141
+ document.body.style.overflow = prevOverflow;
142
+ window.removeEventListener('resize', updatePosition);
143
+ };
144
+ }, [open, updatePosition]);
145
+
146
+ // Close on outside click
147
+ React.useEffect(function () {
148
+ if (!open) return;
149
+ var handleMouseDown = function handleMouseDown(e) {
150
+ var _triggerRef$current, _popoverRef$current2;
151
+ var target = e.target;
152
+ if ((_triggerRef$current = triggerRef.current) !== null && _triggerRef$current !== void 0 && _triggerRef$current.contains(target) || (_popoverRef$current2 = popoverRef.current) !== null && _popoverRef$current2 !== void 0 && _popoverRef$current2.contains(target)) {
153
+ return;
154
+ }
155
+ setOpen(false);
156
+ setSearch('');
157
+ };
158
+ document.addEventListener('mousedown', handleMouseDown);
159
+ return function () {
160
+ return document.removeEventListener('mousedown', handleMouseDown);
161
+ };
162
+ }, [open]);
163
+
164
+ // Close on Escape
165
+ React.useEffect(function () {
166
+ if (!open) return;
167
+ var handleKeyDown = function handleKeyDown(e) {
168
+ if (e.key === 'Escape') {
169
+ setOpen(false);
170
+ setSearch('');
171
+ }
172
+ };
173
+ document.addEventListener('keydown', handleKeyDown);
174
+ return function () {
175
+ return document.removeEventListener('keydown', handleKeyDown);
176
+ };
177
+ }, [open]);
178
+ var handleSelect = React.useCallback(function (icon) {
179
+ if (!isControlled) setInternalValue(icon.name);
180
+ onChange === null || onChange === void 0 || onChange(icon.name);
181
+ setOpen(false);
182
+ setSearch('');
183
+ }, [isControlled, onChange]);
184
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
185
+ ref: triggerRef,
186
+ className: "cop-inline-flex"
187
+ }, /*#__PURE__*/React.createElement(BaseButton, {
188
+ variant: "minimal",
189
+ disabled: disabled,
190
+ onClick: function onClick() {
191
+ return setOpen(function (prev) {
192
+ return !prev;
193
+ });
194
+ },
195
+ "aria-label": selectedIcon !== null && selectedIcon !== void 0 ? selectedIcon : placeholder,
196
+ "aria-haspopup": "dialog",
197
+ "aria-expanded": open,
198
+ className: cn('cop-p-[7px]', className)
199
+ }, selectedIconData ? selectedIconData.source === 'ds' ? /*#__PURE__*/React.createElement(selectedIconData.Component, {
200
+ className: "cop-h-4 cop-w-4",
201
+ "aria-hidden": "true"
202
+ }) : /*#__PURE__*/React.createElement("svg", {
203
+ viewBox: selectedIconData.viewBox,
204
+ fill: "currentColor",
205
+ className: "cop-h-4 cop-w-4",
206
+ overflow: "visible",
207
+ "aria-hidden": "true"
208
+ }, /*#__PURE__*/React.createElement("path", {
209
+ d: selectedIconData.path
210
+ })) : /*#__PURE__*/React.createElement("span", {
211
+ className: "cop-text-muted-foreground cop-text-sm"
212
+ }, placeholder))), open && /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement("div", {
213
+ ref: popoverRef,
214
+ style: {
215
+ top: coords.top,
216
+ left: coords.left
217
+ },
218
+ className: "cop-fixed cop-z-1500 cop-w-[330px] cop-overflow-clip cop-rounded cop-border cop-border-solid cop-border-[#eff1f4] cop-bg-white cop-shadow-[0px_6px_20px_0px_rgba(0,0,0,0.07)]"
219
+ }, /*#__PURE__*/React.createElement("div", {
220
+ className: "cop-px-3 cop-pt-3"
221
+ }, /*#__PURE__*/React.createElement(Input, {
222
+ ref: searchInputRef,
223
+ placeholder: "Search icons",
224
+ value: search,
225
+ onChange: function onChange(e) {
226
+ return setSearch(e.target.value);
227
+ },
228
+ onFocus: function onFocus() {
229
+ var _gridRef$current, _gridRef$current$rese;
230
+ return (_gridRef$current = gridRef.current) === null || _gridRef$current === void 0 || (_gridRef$current$rese = _gridRef$current.resetFocus) === null || _gridRef$current$rese === void 0 ? void 0 : _gridRef$current$rese.call(_gridRef$current);
231
+ },
232
+ onKeyDown: function onKeyDown(e) {
233
+ if ((e.key === 'ArrowDown' || e.key === 'Enter') && filteredIcons.length > 0) {
234
+ var _gridRef$current2;
235
+ e.preventDefault();
236
+ (_gridRef$current2 = gridRef.current) === null || _gridRef$current2 === void 0 || _gridRef$current2.focusFirst();
237
+ }
238
+ },
239
+ className: "focus-visible:cop-border-[#1a1a1a] focus-visible:cop-ring-0 focus-visible:cop-ring-offset-0"
240
+ })), /*#__PURE__*/React.createElement("div", {
241
+ className: "cop-py-3 cop-pl-3 cop-pr-0"
242
+ }, /*#__PURE__*/React.createElement(IconGrid, {
243
+ ref: gridRef,
244
+ icons: filteredIcons,
245
+ onSelect: handleSelect,
246
+ selectedIcon: selectedIcon,
247
+ onEscapeFromGrid: function onEscapeFromGrid() {
248
+ var _searchInputRef$curre2;
249
+ return (_searchInputRef$curre2 = searchInputRef.current) === null || _searchInputRef$curre2 === void 0 ? void 0 : _searchInputRef$curre2.focus();
250
+ }
251
+ }))), document.body));
252
+ });
253
+ IconPicker.displayName = 'IconPicker';
254
+ export { IconPicker };
@@ -0,0 +1,55 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ import { searchIcons } from "../searchIcons";
3
+ jest.mock("../icons.generated", function () {
4
+ return {
5
+ searchTermsMap: {
6
+ heart: ['love', 'like', 'favorite'],
7
+ 'heart-crack': ['break', 'love', 'sad'],
8
+ star: ['favorite', 'rating'],
9
+ house: ['home', 'building'],
10
+ 'magnifying-glass': ['search', 'find', 'look'],
11
+ gear: ['settings', 'configure']
12
+ }
13
+ };
14
+ });
15
+ var faIcon = function faIcon(name) {
16
+ return {
17
+ source: 'fa',
18
+ name: name,
19
+ viewBox: '0 0 512 512',
20
+ path: 'M0 0'
21
+ };
22
+ };
23
+ var fixture = [faIcon('heart'), faIcon('heart-crack'), faIcon('star'), faIcon('house'), faIcon('magnifying-glass'), faIcon('filter'), faIcon('gear')];
24
+ describe('searchIcons', function () {
25
+ it('returns all icons for an empty query', function () {
26
+ expect(searchIcons('', fixture)).toEqual(fixture);
27
+ expect(searchIcons(' ', fixture)).toEqual(fixture);
28
+ });
29
+ it('matches icons by name', function () {
30
+ var results = searchIcons('heart', fixture);
31
+ expect(results).toHaveLength(2);
32
+ expect(results.map(function (i) {
33
+ return i.name;
34
+ })).toEqual(['heart', 'heart-crack']);
35
+ });
36
+ it('matches icons by searchTerms', function () {
37
+ var results = searchIcons('love', fixture);
38
+ expect(results).toHaveLength(2);
39
+ expect(results.map(function (i) {
40
+ return i.name;
41
+ })).toEqual(['heart', 'heart-crack']);
42
+ });
43
+ it('matches icons by partial prefix', function () {
44
+ var _results$;
45
+ var results = searchIcons('mag', fixture);
46
+ expect(results).toHaveLength(1);
47
+ expect((_results$ = results[0]) === null || _results$ === void 0 ? void 0 : _results$.name).toBe('magnifying-glass');
48
+ });
49
+ it('matches icons by search term prefix', function () {
50
+ var _results$2;
51
+ var results = searchIcons('set', fixture);
52
+ expect(results).toHaveLength(1);
53
+ expect((_results$2 = results[0]) === null || _results$2 === void 0 ? void 0 : _results$2.name).toBe('gear');
54
+ });
55
+ });
@@ -0,0 +1,18 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
8
+ import * as icons from "../../icons";
9
+ export var dsPickerIcons = Object.entries(icons).map(function (_ref) {
10
+ var _ref2 = _slicedToArray(_ref, 2),
11
+ name = _ref2[0],
12
+ Component = _ref2[1];
13
+ return {
14
+ source: 'ds',
15
+ name: name,
16
+ Component: Component
17
+ };
18
+ });