@assembly-js/design-system 3.1.11 → 3.1.14

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 (45) hide show
  1. package/dist/esm/components/UserCompanySelector/ListItem.js +12 -9
  2. package/dist/esm/components/UserCompanySelector/SelectedValueChip.js +5 -1
  3. package/dist/esm/components/UserCompanySelector/UserCompanySelector.js +36 -46
  4. package/dist/esm/components/UserCompanySelector/__tests__/getClassNames.test.js +81 -0
  5. package/dist/esm/components/UserCompanySelector/__tests__/getOptionRelevanceScore.test.js +115 -0
  6. package/dist/esm/components/UserCompanySelector/data.js +17 -15
  7. package/dist/esm/components/UserCompanySelector/getClassNames.js +44 -0
  8. package/dist/esm/components/UserCompanySelector/getOptionRelevanceScore.js +33 -0
  9. package/dist/esm/types/components/UserCompanySelector/ListItem.d.ts +8 -3
  10. package/dist/esm/types/components/UserCompanySelector/UserCompanySelector.stories.d.ts +2 -0
  11. package/dist/esm/types/components/UserCompanySelector/__tests__/getClassNames.test.d.ts +1 -0
  12. package/dist/esm/types/components/UserCompanySelector/__tests__/getOptionRelevanceScore.test.d.ts +1 -0
  13. package/dist/esm/types/components/UserCompanySelector/getClassNames.d.ts +11 -0
  14. package/dist/esm/types/components/UserCompanySelector/getOptionRelevanceScore.d.ts +14 -0
  15. package/dist/esm/types/components/UserCompanySelector/types.d.ts +11 -0
  16. package/dist/esm/types/index.d.ts +1 -1
  17. package/dist/esm/types/tsconfig.tsbuildinfo +1 -1
  18. package/dist/styles/main.css +1 -1
  19. package/dist/types/components/UserCompanySelector/ListItem.d.ts +8 -3
  20. package/dist/types/components/UserCompanySelector/UserCompanySelector.stories.d.ts +2 -0
  21. package/dist/types/components/UserCompanySelector/__tests__/getClassNames.test.d.ts +1 -0
  22. package/dist/types/components/UserCompanySelector/__tests__/getOptionRelevanceScore.test.d.ts +1 -0
  23. package/dist/types/components/UserCompanySelector/getClassNames.d.ts +11 -0
  24. package/dist/types/components/UserCompanySelector/getOptionRelevanceScore.d.ts +14 -0
  25. package/dist/types/components/UserCompanySelector/types.d.ts +11 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/tsconfig.tsbuildinfo +1 -1
  28. package/dist/umd/components/UserCompanySelector/ListItem.js +15 -12
  29. package/dist/umd/components/UserCompanySelector/SelectedValueChip.js +5 -1
  30. package/dist/umd/components/UserCompanySelector/UserCompanySelector.js +38 -50
  31. package/dist/umd/components/UserCompanySelector/__tests__/getClassNames.test.js +96 -0
  32. package/dist/umd/components/UserCompanySelector/__tests__/getOptionRelevanceScore.test.js +130 -0
  33. package/dist/umd/components/UserCompanySelector/data.js +17 -15
  34. package/dist/umd/components/UserCompanySelector/getClassNames.js +64 -0
  35. package/dist/umd/components/UserCompanySelector/getOptionRelevanceScore.js +53 -0
  36. package/dist/umd/types/components/UserCompanySelector/ListItem.d.ts +8 -3
  37. package/dist/umd/types/components/UserCompanySelector/UserCompanySelector.stories.d.ts +2 -0
  38. package/dist/umd/types/components/UserCompanySelector/__tests__/getClassNames.test.d.ts +1 -0
  39. package/dist/umd/types/components/UserCompanySelector/__tests__/getOptionRelevanceScore.test.d.ts +1 -0
  40. package/dist/umd/types/components/UserCompanySelector/getClassNames.d.ts +11 -0
  41. package/dist/umd/types/components/UserCompanySelector/getOptionRelevanceScore.d.ts +14 -0
  42. package/dist/umd/types/components/UserCompanySelector/types.d.ts +11 -0
  43. package/dist/umd/types/index.d.ts +1 -1
  44. package/dist/umd/types/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +1 -1
@@ -3,15 +3,16 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
3
3
  import React from 'react';
4
4
  import { Avatar } from "../Avatar/Avatar";
5
5
  import { Body } from "../Typography";
6
- import clsx from 'clsx';
7
6
  import { UserWithCompanyAvatar } from "../Avatar/UserWithCompanyAvatar";
8
- import { getInitials } from "../../common/utils";
9
- export function ListItem(_ref) {
7
+ import { cn, getInitials } from "../../common/utils";
8
+ export function ListItem(props) {
10
9
  var _option$avatarSrc, _option$avatarFallbac, _option$label;
11
- var option = _ref.data,
12
- innerRef = _ref.innerRef,
13
- isFocused = _ref.isFocused,
14
- innerProps = _ref.innerProps;
10
+ var option = props.data,
11
+ innerRef = props.innerRef,
12
+ isFocused = props.isFocused,
13
+ isSelected = props.isSelected,
14
+ innerProps = props.innerProps,
15
+ listClassName = props.listClassName;
15
16
  var avatarSrc = option.avatarSrc,
16
17
  avatarFallbackColor = option.avatarFallbackColor,
17
18
  label = option.label,
@@ -23,13 +24,15 @@ export function ListItem(_ref) {
23
24
  key: option.value,
24
25
  ref: innerRef
25
26
  }, innerProps, {
26
- className: clsx('cop-flex cop-cursor-pointer cop-items-center cop-gap-3 cop-px-3 hover:cop-bg-gray-100', {
27
+ "data-focused": isFocused || undefined,
28
+ "data-selected": isSelected || undefined,
29
+ className: cn('cop-flex cop-cursor-pointer cop-items-center cop-gap-3 cop-px-3 hover:cop-bg-gray-100', {
27
30
  'cop-h-10': !!companyName,
28
31
  // if an option has company name to render, the option should have a height of 40px
29
32
  'cop-h-8': !companyName,
30
33
  // if an option has no company name to render, the option should have a height of 32px
31
34
  'cop-bg-primary': isFocused
32
- })
35
+ }, listClassName)
33
36
  }), option.type !== 'company' ? /*#__PURE__*/React.createElement(UserWithCompanyAvatar, {
34
37
  size: "sm",
35
38
  avatarSrc: (_option$avatarSrc = option.avatarSrc) !== null && _option$avatarSrc !== void 0 ? _option$avatarSrc : '',
@@ -23,8 +23,12 @@ export function SelectedValueChip(props) {
23
23
  }
24
24
  };
25
25
  return /*#__PURE__*/React.createElement(Chip, {
26
- className: "!cop-rounded cop-border cop-border-solid cop-py-[5px] cop-max-w-[200px]",
26
+ className: "cop-max-w-[200px] !cop-rounded cop-border cop-border-solid cop-py-[5px]",
27
27
  variant: "outlined",
28
+ onClick: function onClick() {
29
+ return null;
30
+ } // passing onclick would allow showing the hover state - this is a temporary solution avoiding doing big changes in the chip component might causing regressions
31
+ ,
28
32
  truncate: true,
29
33
  onClose: handleRemove,
30
34
  prefixIcon: option.type === 'company' ? /*#__PURE__*/React.createElement(Avatar, {
@@ -2,7 +2,7 @@
2
2
  'use client';
3
3
 
4
4
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
5
- var _excluded = ["autoFocus", "grouped", "name", "internalUsers", "clientUsers", "companies", "onChange", "onBlur", "placeholder", "styles", "ignoreCompanies", "ignoreFilterClientsByCompany", "limitSelectedOptions", "initialValue", "customLabels", "listHeading"];
5
+ var _excluded = ["autoFocus", "grouped", "name", "internalUsers", "clientUsers", "companies", "onChange", "onBlur", "placeholder", "styles", "ignoreCompanies", "ignoreFilterClientsByCompany", "limitSelectedOptions", "initialValue", "customLabels", "listHeading", "listClassName", "variant"];
6
6
  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); }
7
7
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
8
8
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -29,6 +29,8 @@ import { Heading, Body } from "../Typography";
29
29
  import { SelectedValueChip } from "./SelectedValueChip";
30
30
  import { LimitedOptionsList } from "./LimitedOptionsList";
31
31
  import { colors } from "../../theme/colors";
32
+ import { getClassNames } from "./getClassNames";
33
+ import { getOptionRelevanceScore } from "./getOptionRelevanceScore";
32
34
  var MAX_OPTIONS = {
33
35
  flat: 6,
34
36
  group: 3
@@ -78,6 +80,9 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
78
80
  initialValue = _ref.initialValue,
79
81
  customLabels = _ref.customLabels,
80
82
  listHeading = _ref.listHeading,
83
+ listClassName = _ref.listClassName,
84
+ _ref$variant = _ref.variant,
85
+ variant = _ref$variant === void 0 ? 'default' : _ref$variant,
81
86
  others = _objectWithoutProperties(_ref, _excluded);
82
87
  var limitSelectedOptions = limitSelectedOptionsUnvalidated === 0 ? 1000 : Math.min(1000, limitSelectedOptionsUnvalidated);
83
88
  var companiesMap = useMemo(function () {
@@ -101,29 +106,9 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
101
106
  _useState4 = _slicedToArray(_useState3, 2),
102
107
  inputValue = _useState4[0],
103
108
  setInputValue = _useState4[1];
104
-
105
- /**
106
- * Returns a relevance score for an option based on the input value.
107
- * Higher scores indicate more relevant matches (direct name matches > company name matches).
108
- * @param option - the option to score
109
- * @returns 2 for direct name match, 1 for company name match only, 0 for no match
110
- */
111
- var getOptionRelevanceScore = useCallback(function (option) {
112
- if (!inputValue) {
113
- return 1; // Show all options when no input
114
- }
115
- var lowerInput = inputValue.toLowerCase();
116
- var nameMatches = option.label.toLowerCase().includes(lowerInput);
117
- if (option.type === 'client') {
118
- var _option$companyId, _company$label;
119
- var company = companiesMap.get((_option$companyId = option.companyId) !== null && _option$companyId !== void 0 ? _option$companyId : '');
120
- var companyMatches = company === null || company === void 0 || (_company$label = company.label) === null || _company$label === void 0 ? void 0 : _company$label.toLowerCase().includes(lowerInput);
121
- if (nameMatches) return 2; // Direct name match - highest priority
122
- if (companyMatches) return 1; // Company name match - lower priority
123
- return 0; // No match
124
- }
125
- return nameMatches ? 1 : 0;
126
- }, [inputValue, companiesMap]);
109
+ var scoreOption = useCallback(function (option) {
110
+ return getOptionRelevanceScore(option, inputValue, companiesMap, ignoreCompanies);
111
+ }, [inputValue, companiesMap, ignoreCompanies]);
127
112
  var isOptionSelected = useCallback(function (option) {
128
113
  return selectedOptions.some(function (o) {
129
114
  return o.value === option.value;
@@ -167,7 +152,7 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
167
152
  options: group.options.map(function (opt) {
168
153
  return {
169
154
  opt: opt,
170
- score: getOptionRelevanceScore(opt)
155
+ score: scoreOption(opt)
171
156
  };
172
157
  }).filter(function (_ref2) {
173
158
  var score = _ref2.score;
@@ -183,7 +168,7 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
183
168
  var filteredFlatOptions = [].concat(_toConsumableArray(filteredInternalUsers), _toConsumableArray(filteredClients), _toConsumableArray(filteredCompanies)).map(function (opt) {
184
169
  return {
185
170
  opt: opt,
186
- score: getOptionRelevanceScore(opt)
171
+ score: scoreOption(opt)
187
172
  };
188
173
  }).filter(function (_ref4) {
189
174
  var score = _ref4.score;
@@ -198,7 +183,7 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
198
183
  // when grouped is true then we need to show the options list in a group manner.
199
184
  var opts = grouped ? filteredGroupedOptions : filteredFlatOptions;
200
185
  return opts;
201
- }, [internalUsers, clientUsers, companies, grouped, selectedOptions, ignoreFilterClientsByCompany, limitSelectedOptions, getOptionRelevanceScore, isOptionSelected, customLabels]);
186
+ }, [internalUsers, clientUsers, companies, grouped, selectedOptions, ignoreFilterClientsByCompany, limitSelectedOptions, scoreOption, isOptionSelected, customLabels]);
202
187
 
203
188
  /**
204
189
  * Handles the change of the selected options
@@ -253,8 +238,8 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
253
238
  * @returns the option with the company data
254
239
  */
255
240
  var getCompanyData = useCallback(function (option) {
256
- var _option$companyId2, _clientToCompanyCount2, _option$value;
257
- var company = option.type === 'client' ? companiesMap.get((_option$companyId2 = option.companyId) !== null && _option$companyId2 !== void 0 ? _option$companyId2 : '') : undefined;
241
+ var _option$companyId, _clientToCompanyCount2, _option$value;
242
+ var company = option.type === 'client' ? companiesMap.get((_option$companyId = option.companyId) !== null && _option$companyId !== void 0 ? _option$companyId : '') : undefined;
258
243
 
259
244
  // only setup the company avatar data if the client is part of more than one company
260
245
  var clientsCount = (_clientToCompanyCount2 = clientToCompanyCount.get((_option$value = option.value) !== null && _option$value !== void 0 ? _option$value : '')) !== null && _clientToCompanyCount2 !== void 0 ? _clientToCompanyCount2 : 0;
@@ -265,11 +250,12 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
265
250
  } : {});
266
251
  }, [companiesMap, clientToCompanyCount]);
267
252
  var handleFilterOption = useCallback(function (option, inputValue) {
253
+ var _option$data$label$to, _option$data$label;
268
254
  var lowerCaseInputValue = inputValue.toLowerCase();
269
255
  if (ignoreCompanies && option.data.type === 'company') {
270
256
  return false;
271
257
  }
272
- var optionLabel = option.data.label.toLowerCase();
258
+ var optionLabel = (_option$data$label$to = (_option$data$label = option.data.label) === null || _option$data$label === void 0 ? void 0 : _option$data$label.toLowerCase()) !== null && _option$data$label$to !== void 0 ? _option$data$label$to : '';
273
259
  // for clients option, we should filter by company name too
274
260
  // e.g. if a client company is ACME and the input is ACME, the result should
275
261
  // show the client with the company ACME and the client with the company name
@@ -307,7 +293,8 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
307
293
  },
308
294
  Option: function Option(props) {
309
295
  return /*#__PURE__*/React.createElement(ListItem, _extends({}, props, {
310
- data: getCompanyData(props.data)
296
+ data: getCompanyData(props.data),
297
+ listClassName: listClassName
311
298
  }));
312
299
  },
313
300
  DropdownIndicator: function DropdownIndicator() {
@@ -330,7 +317,10 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
330
317
  }));
331
318
  }
332
319
  };
333
- }, [getCompanyData]);
320
+ }, [getCompanyData, listHeading, listClassName]);
321
+ var variantClassNames = useMemo(function () {
322
+ return getClassNames(variant);
323
+ }, [variant]);
334
324
  return /*#__PURE__*/React.createElement(Select, _extends({
335
325
  autoFocus: autoFocus,
336
326
  isMulti: true,
@@ -344,7 +334,7 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
344
334
  placeholder: placeholder,
345
335
  filterOption: handleFilterOption,
346
336
  unstyled: true,
347
- styles: _objectSpread({
337
+ styles: _objectSpread(_objectSpread({
348
338
  clearIndicator: function clearIndicator() {
349
339
  return {
350
340
  display: 'none' // hide the clear button. Unfortunatelly, the class specificity is not enough to override the default styles.
@@ -355,20 +345,20 @@ export var UserCompanySelector = function UserCompanySelector(_ref) {
355
345
  color: colors.gray[500]
356
346
  });
357
347
  }
358
- }, styles),
359
- classNames: {
360
- valueContainer: function valueContainer() {
361
- return 'cop-flex cop-gap-1';
362
- },
363
- input: function input() {
364
- return 'cop-text-base cop-text-primary hover:cop-cursor-text';
365
- },
366
- menuList: function menuList() {
367
- return 'cop-relative cop-bg-white cop-shadow-popover-50 cop-rounded cop-border cop-border-solid cop-border-secondary cop-mt-1';
368
- },
369
- control: function control() {
370
- return 'cop-border-solid cop-border-[1px] cop-border-secondary focus-within:cop-border-primary cop-px-3 cop-py-[2px] cop-rounded';
348
+ }, variant === 'unified' ? {
349
+ menu: function menu() {
350
+ return {
351
+ position: 'static'
352
+ };
371
353
  }
354
+ } : {}), styles),
355
+ classNames: {
356
+ container: variantClassNames.container,
357
+ valueContainer: variantClassNames.valueContainer,
358
+ input: variantClassNames.input,
359
+ placeholder: variantClassNames.placeholder,
360
+ menuList: variantClassNames.menuList,
361
+ control: variantClassNames.control
372
362
  },
373
363
  components: memoizedComponents,
374
364
  value: selectedOptions
@@ -0,0 +1,81 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ import { getClassNames } from "../getClassNames";
3
+ describe('getClassNames', function () {
4
+ describe('default variant', function () {
5
+ var cls = getClassNames('default');
6
+ it('control has outer border, rounded, and focus-within blue border', function () {
7
+ var control = cls.control();
8
+ expect(control).toContain('cop-border-solid');
9
+ expect(control).toContain('cop-border-[1px]');
10
+ expect(control).toContain('cop-border-secondary');
11
+ expect(control).toContain('cop-rounded');
12
+ expect(control).toContain('focus-within:cop-border-primary');
13
+ });
14
+ it('menuList has its own border, shadow, rounded, and top margin', function () {
15
+ var menuList = cls.menuList();
16
+ expect(menuList).toContain('cop-shadow-popover-50');
17
+ expect(menuList).toContain('cop-rounded');
18
+ expect(menuList).toContain('cop-border');
19
+ expect(menuList).toContain('cop-border-secondary');
20
+ expect(menuList).toContain('cop-mt-1');
21
+ });
22
+ it('container returns undefined (no outer wrapper in default)', function () {
23
+ expect(cls.container).toBeUndefined();
24
+ });
25
+ });
26
+ describe('unified variant', function () {
27
+ var cls = getClassNames('unified');
28
+ it('container has border, rounded corners, and shadow', function () {
29
+ var _cls$container, _cls$container2;
30
+ var container = (_cls$container = (_cls$container2 = cls.container) === null || _cls$container2 === void 0 ? void 0 : _cls$container2.call(cls)) !== null && _cls$container !== void 0 ? _cls$container : '';
31
+ expect(container).toContain('cop-border');
32
+ expect(container).toContain('cop-border-secondary');
33
+ expect(container).toContain('cop-rounded');
34
+ expect(container).toContain('cop-shadow-popover-50');
35
+ expect(container).toContain('cop-overflow-hidden');
36
+ });
37
+ it('control has no border (outer container owns all border styling)', function () {
38
+ var control = cls.control();
39
+ expect(control).toContain('cop-px-3');
40
+ expect(control).toContain('cop-py-[2px]');
41
+ expect(control).not.toContain('cop-border');
42
+ expect(control).not.toContain('cop-rounded');
43
+ expect(control).not.toContain('focus-within:cop-border-primary');
44
+ });
45
+ it('menuList has no border, no shadow, no rounded, no top margin', function () {
46
+ var menuList = cls.menuList();
47
+ expect(menuList).not.toContain('cop-shadow-popover-50');
48
+ expect(menuList).not.toContain('cop-rounded');
49
+ expect(menuList).not.toContain('cop-border-solid');
50
+ expect(menuList).not.toContain('cop-border-secondary');
51
+ expect(menuList).not.toContain('cop-mt-1');
52
+ });
53
+ });
54
+ describe('shared across variants', function () {
55
+ it('input uses 13px font size with 20px line height in default', function () {
56
+ var input = getClassNames('default').input();
57
+ expect(input).toContain('cop-text-[13px]');
58
+ expect(input).toContain('cop-leading-5');
59
+ });
60
+ it('input uses 13px font size with 20px line height in unified', function () {
61
+ var input = getClassNames('unified').input();
62
+ expect(input).toContain('cop-text-[13px]');
63
+ expect(input).toContain('cop-leading-5');
64
+ });
65
+ it('placeholder uses 13px font size with 20px line height in default', function () {
66
+ var placeholder = getClassNames('default').placeholder();
67
+ expect(placeholder).toContain('cop-text-[13px]');
68
+ expect(placeholder).toContain('cop-leading-5');
69
+ });
70
+ it('placeholder uses 13px font size with 20px line height in unified', function () {
71
+ var placeholder = getClassNames('unified').placeholder();
72
+ expect(placeholder).toContain('cop-text-[13px]');
73
+ expect(placeholder).toContain('cop-leading-5');
74
+ });
75
+ it('valueContainer is the same flex gap class in both variants', function () {
76
+ expect(getClassNames('default').valueContainer()).toBe(getClassNames('unified').valueContainer());
77
+ expect(getClassNames('default').valueContainer()).toContain('cop-flex');
78
+ expect(getClassNames('default').valueContainer()).toContain('cop-gap-1');
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,115 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
6
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
7
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
8
+ import { getOptionRelevanceScore } from "../getOptionRelevanceScore";
9
+ var company = {
10
+ value: 'company-1',
11
+ label: 'Acme Inc',
12
+ type: 'company'
13
+ };
14
+ var client = {
15
+ value: 'client-1',
16
+ label: 'Jane Doe',
17
+ type: 'client',
18
+ companyId: 'company-1'
19
+ };
20
+ var internalUser = {
21
+ value: 'user-1',
22
+ label: 'John Smith',
23
+ type: 'internalUser'
24
+ };
25
+ var companiesMap = new Map([[company.value, company]]);
26
+ describe('getOptionRelevanceScore', function () {
27
+ describe('empty input', function () {
28
+ it('returns 1 (show all) when input is empty', function () {
29
+ expect(getOptionRelevanceScore(client, '', companiesMap)).toBe(1);
30
+ expect(getOptionRelevanceScore(company, '', companiesMap)).toBe(1);
31
+ expect(getOptionRelevanceScore(internalUser, '', companiesMap)).toBe(1);
32
+ });
33
+ });
34
+ describe('client options', function () {
35
+ it('returns 2 for a direct name match', function () {
36
+ expect(getOptionRelevanceScore(client, 'jane', companiesMap)).toBe(2);
37
+ });
38
+ it('returns 1 for a company name match only', function () {
39
+ expect(getOptionRelevanceScore(client, 'acme', companiesMap)).toBe(1);
40
+ });
41
+ it('prefers the direct name match (2) over a company match', function () {
42
+ var acmeClient = _objectSpread(_objectSpread({}, client), {}, {
43
+ label: 'Acme Person'
44
+ });
45
+ expect(getOptionRelevanceScore(acmeClient, 'acme', companiesMap)).toBe(2);
46
+ });
47
+ it('returns 0 when neither name nor company match', function () {
48
+ expect(getOptionRelevanceScore(client, 'zzz', companiesMap)).toBe(0);
49
+ });
50
+ it('matches case-insensitively', function () {
51
+ expect(getOptionRelevanceScore(client, 'JANE', companiesMap)).toBe(2);
52
+ });
53
+ it('returns 0 for a company match when the company is missing from the map', function () {
54
+ expect(getOptionRelevanceScore(client, 'acme', new Map())).toBe(0);
55
+ });
56
+ });
57
+ describe('non-client options', function () {
58
+ it('returns 1 for a matching company', function () {
59
+ expect(getOptionRelevanceScore(company, 'acme', companiesMap)).toBe(1);
60
+ });
61
+ it('returns 0 for a non-matching company', function () {
62
+ expect(getOptionRelevanceScore(company, 'zzz', companiesMap)).toBe(0);
63
+ });
64
+ it('returns 1 for a matching internal user', function () {
65
+ expect(getOptionRelevanceScore(internalUser, 'john', companiesMap)).toBe(1);
66
+ });
67
+ });
68
+ describe('ignoreCompanies', function () {
69
+ it('returns 0 for company options when ignoreCompanies is true, even on a match', function () {
70
+ expect(getOptionRelevanceScore(company, 'acme', companiesMap, true)).toBe(0);
71
+ });
72
+ it('returns 0 for company options when ignoreCompanies is true and input is empty', function () {
73
+ expect(getOptionRelevanceScore(company, '', companiesMap, true)).toBe(0);
74
+ });
75
+ it('still scores client options normally when ignoreCompanies is true', function () {
76
+ expect(getOptionRelevanceScore(client, 'jane', companiesMap, true)).toBe(2);
77
+ });
78
+ });
79
+ describe('null-safety (bad runtime data)', function () {
80
+ it('does not throw and returns 0 when a client label is null', function () {
81
+ var badClient = _objectSpread(_objectSpread({}, client), {}, {
82
+ label: null
83
+ });
84
+ expect(function () {
85
+ return getOptionRelevanceScore(badClient, 'jane', companiesMap);
86
+ }).not.toThrow();
87
+ // name does not match (null), company "Acme" does not match "jane" -> 0
88
+ expect(getOptionRelevanceScore(badClient, 'jane', companiesMap)).toBe(0);
89
+ });
90
+ it('falls back to the company match when a client label is null', function () {
91
+ var badClient = _objectSpread(_objectSpread({}, client), {}, {
92
+ label: undefined
93
+ });
94
+ expect(getOptionRelevanceScore(badClient, 'acme', companiesMap)).toBe(1);
95
+ });
96
+ it('does not throw and returns 0 when a company label is null', function () {
97
+ var badCompany = _objectSpread(_objectSpread({}, company), {}, {
98
+ label: null
99
+ });
100
+ expect(function () {
101
+ return getOptionRelevanceScore(badCompany, 'acme', companiesMap);
102
+ }).not.toThrow();
103
+ expect(getOptionRelevanceScore(badCompany, 'acme', companiesMap)).toBe(0);
104
+ });
105
+ it('does not throw when the matching company in the map has a null label', function () {
106
+ var mapWithBadCompany = new Map([[company.value, _objectSpread(_objectSpread({}, company), {}, {
107
+ label: null
108
+ })]]);
109
+ expect(function () {
110
+ return getOptionRelevanceScore(client, 'acme', mapWithBadCompany);
111
+ }).not.toThrow();
112
+ expect(getOptionRelevanceScore(client, 'acme', mapWithBadCompany)).toBe(0);
113
+ });
114
+ });
115
+ });
@@ -3,14 +3,19 @@
3
3
  * This file is used to generate the data for the combo select stories
4
4
  */
5
5
 
6
- function generateRandomSrc() {
7
- return "https://i.pravatar.cc/150?img=".concat(Math.floor(Math.random() * 70) + 1);
6
+ // Deterministic avatar src so Storybook/Chromatic snapshots are stable across builds.
7
+ // pravatar exposes images 1-70, so we cycle through that range based on the item index.
8
+ function generateSrc(index) {
9
+ return "https://i.pravatar.cc/150?img=".concat(index % 70 + 1);
8
10
  }
9
11
  var firstNames = ['John', 'Jane', 'Alice', 'Bob', 'Lara', 'Mike', 'Sarah', 'David', 'Emily', 'Daniel', 'Chris', 'Olivia', 'James', 'Sophia', 'Henry', 'Emma', 'Liam', 'Mia', 'Noah', 'Zoe'];
10
12
  var lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'Garcia', 'Martinez', 'Lopez', 'Clark', 'Lewis', 'Walker', 'Young', 'Allen', 'King', 'Wright', 'Scott', 'Torres', 'Nguyen'];
11
13
 
12
- // Generate unique name combinations
14
+ // Generate unique name combinations deterministically.
15
+ // `offset` lets callers pick a different, non-overlapping slice of the same
16
+ // stable ordering so internal and client lists don't share identical names.
13
17
  function generateUniqueNames(count) {
18
+ var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
14
19
  var combinations = [];
15
20
  for (var _i = 0, _firstNames = firstNames; _i < _firstNames.length; _i++) {
16
21
  var first = _firstNames[_i];
@@ -19,16 +24,12 @@ function generateUniqueNames(count) {
19
24
  combinations.push("".concat(first, " ").concat(last));
20
25
  }
21
26
  }
22
- // Shuffle and slice to the required count
23
- var shuffled = combinations.sort(function () {
24
- return 0.5 - Math.random();
25
- });
26
- return shuffled.slice(0, count);
27
+ return combinations.slice(offset, offset + count);
27
28
  }
28
29
 
29
- // Get 100 unique names for internal and client users
30
+ // Get 100 unique names for internal and client users (non-overlapping slices)
30
31
  var internalUserNames = generateUniqueNames(100);
31
- var clientUserNames = generateUniqueNames(100);
32
+ var clientUserNames = generateUniqueNames(100, 100);
32
33
 
33
34
  // Generate companies
34
35
  var companies = Array.from({
@@ -37,7 +38,7 @@ var companies = Array.from({
37
38
  return {
38
39
  value: "company-".concat(i + 1),
39
40
  label: "Company ".concat(i + 1),
40
- avatarSrc: generateRandomSrc(),
41
+ avatarSrc: generateSrc(i),
41
42
  type: 'company'
42
43
  };
43
44
  });
@@ -47,20 +48,21 @@ var internalUsers = internalUserNames.map(function (name, i) {
47
48
  return {
48
49
  value: "internal-".concat(i + 1),
49
50
  label: name,
50
- avatarSrc: generateRandomSrc(),
51
+ avatarSrc: generateSrc(i),
51
52
  type: 'internalUser'
52
53
  };
53
54
  });
54
55
 
55
56
  // Generate client users (with valid companyIds)
56
57
  var clientUsers = clientUserNames.map(function (name, i) {
57
- var _companies$Math$floor;
58
+ var _companies;
58
59
  return {
59
60
  value: "client-".concat(i + 1),
60
61
  label: name,
61
- avatarSrc: generateRandomSrc(),
62
+ avatarSrc: generateSrc(i),
62
63
  type: 'client',
63
- companyId: (_companies$Math$floor = companies[Math.floor(Math.random() * companies.length)]) === null || _companies$Math$floor === void 0 ? void 0 : _companies$Math$floor.value
64
+ // Deterministically distribute clients across companies for stable snapshots.
65
+ companyId: (_companies = companies[i % companies.length]) === null || _companies === void 0 ? void 0 : _companies.value
64
66
  };
65
67
  });
66
68
  export { companies, internalUsers, clientUsers };
@@ -0,0 +1,44 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
6
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
7
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
8
+ var INPUT_CLASSES = 'cop-text-[13px] cop-leading-5 cop-text-primary hover:cop-cursor-text';
9
+ var PLACEHOLDER_CLASSES = 'cop-text-[13px] cop-leading-5';
10
+ var VALUE_CONTAINER_CLASSES = 'cop-flex cop-gap-1';
11
+ export var getClassNames = function getClassNames(variant) {
12
+ var shared = {
13
+ input: function input() {
14
+ return INPUT_CLASSES;
15
+ },
16
+ placeholder: function placeholder() {
17
+ return PLACEHOLDER_CLASSES;
18
+ },
19
+ valueContainer: function valueContainer() {
20
+ return VALUE_CONTAINER_CLASSES;
21
+ }
22
+ };
23
+ if (variant === 'unified') {
24
+ return _objectSpread(_objectSpread({}, shared), {}, {
25
+ container: function container() {
26
+ return 'cop-border cop-border-solid cop-border-secondary cop-rounded cop-shadow-popover-50 cop-overflow-hidden';
27
+ },
28
+ control: function control() {
29
+ return 'cop-px-3 cop-py-[2px]';
30
+ },
31
+ menuList: function menuList() {
32
+ return 'cop-relative cop-bg-white';
33
+ }
34
+ });
35
+ }
36
+ return _objectSpread(_objectSpread({}, shared), {}, {
37
+ control: function control() {
38
+ return 'cop-border-solid cop-border-[1px] cop-border-secondary focus-within:cop-border-primary cop-px-3 cop-py-[2px] cop-rounded';
39
+ },
40
+ menuList: function menuList() {
41
+ return 'cop-relative cop-bg-white cop-shadow-popover-50 cop-rounded cop-border cop-border-solid cop-border-secondary cop-mt-1';
42
+ }
43
+ });
44
+ };
@@ -0,0 +1,33 @@
1
+ // This file is generated by a script. Do not edit this file directly.
2
+ /**
3
+ * Returns a relevance score for an option based on the input value.
4
+ * Higher scores indicate more relevant matches (direct name matches > company name matches).
5
+ *
6
+ * This is a pure function so it can be unit-tested in isolation.
7
+ *
8
+ * @param option - the option to score
9
+ * @param inputValue - the current search input
10
+ * @param companiesMap - lookup of company value -> company option (used to match clients by company name)
11
+ * @param ignoreCompanies - when true, company options are excluded from results entirely
12
+ * @returns 2 for direct name match, 1 for company name match only, 0 for no match
13
+ */
14
+ export var getOptionRelevanceScore = function getOptionRelevanceScore(option, inputValue, companiesMap, ignoreCompanies) {
15
+ var _option$label$toLower, _option$label;
16
+ if (ignoreCompanies && option.type === 'company') {
17
+ return 0; // companies are hidden when ignoreCompanies is set
18
+ }
19
+ if (!inputValue) {
20
+ return 1; // Show all options when no input
21
+ }
22
+ var lowerInput = inputValue.toLowerCase();
23
+ var nameMatches = (_option$label$toLower = (_option$label = option.label) === null || _option$label === void 0 ? void 0 : _option$label.toLowerCase().includes(lowerInput)) !== null && _option$label$toLower !== void 0 ? _option$label$toLower : false;
24
+ if (option.type === 'client') {
25
+ var _option$companyId, _company$label$toLowe, _company$label;
26
+ var company = companiesMap.get((_option$companyId = option.companyId) !== null && _option$companyId !== void 0 ? _option$companyId : '');
27
+ var companyMatches = (_company$label$toLowe = company === null || company === void 0 || (_company$label = company.label) === null || _company$label === void 0 ? void 0 : _company$label.toLowerCase().includes(lowerInput)) !== null && _company$label$toLowe !== void 0 ? _company$label$toLowe : false;
28
+ if (nameMatches) return 2; // Direct name match - highest priority
29
+ if (companyMatches) return 1; // Company name match - lower priority
30
+ return 0; // No match
31
+ }
32
+ return nameMatches ? 1 : 0;
33
+ };
@@ -1,4 +1,9 @@
1
1
  import React from 'react';
2
- import { type OptionWithCompanyMetadata } from './types';
3
- import { type GroupBase, type OptionProps } from 'react-select';
4
- export declare function ListItem<_OptionProps extends OptionWithCompanyMetadata, GroupProps extends GroupBase<_OptionProps> = GroupBase<_OptionProps>>({ data: option, innerRef, isFocused, innerProps, }: OptionProps<_OptionProps, true, GroupProps>): React.JSX.Element;
2
+ import { type OptionWithCompanyMetadata, type UserCompanySelectorOption, type UserCompanySelectorGroupedOption } from './types';
3
+ import { type OptionProps } from 'react-select';
4
+ type ListItemProps = OptionProps<UserCompanySelectorOption, true, UserCompanySelectorGroupedOption> & {
5
+ data: OptionWithCompanyMetadata;
6
+ listClassName?: string;
7
+ };
8
+ export declare function ListItem(props: ListItemProps): React.JSX.Element;
9
+ export {};
@@ -17,3 +17,5 @@ export declare const WithCustomLabels: Story;
17
17
  export declare const WithListHeading: Story;
18
18
  export declare const WithAvatarFallbackColor: Story;
19
19
  export declare const SelectClientInManyCompanies: Story;
20
+ export declare const UnifiedVariant: Story;
21
+ export declare const UnifiedVariantWithPlaceholder: Story;
@@ -0,0 +1,11 @@
1
+ export type UserCompanySelectorVariant = 'default' | 'unified';
2
+ type ClassNamesResult = {
3
+ container?: () => string;
4
+ control: () => string;
5
+ menuList: () => string;
6
+ input: () => string;
7
+ placeholder: () => string;
8
+ valueContainer: () => string;
9
+ };
10
+ export declare const getClassNames: (variant: UserCompanySelectorVariant) => ClassNamesResult;
11
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { UserCompanySelectorOption } from '../../components/UserCompanySelector/types';
2
+ /**
3
+ * Returns a relevance score for an option based on the input value.
4
+ * Higher scores indicate more relevant matches (direct name matches > company name matches).
5
+ *
6
+ * This is a pure function so it can be unit-tested in isolation.
7
+ *
8
+ * @param option - the option to score
9
+ * @param inputValue - the current search input
10
+ * @param companiesMap - lookup of company value -> company option (used to match clients by company name)
11
+ * @param ignoreCompanies - when true, company options are excluded from results entirely
12
+ * @returns 2 for direct name match, 1 for company name match only, 0 for no match
13
+ */
14
+ export declare const getOptionRelevanceScore: (option: UserCompanySelectorOption, inputValue: string, companiesMap: Map<string, UserCompanySelectorOption>, ignoreCompanies?: boolean) => number;