@jobber/components 9.6.2 → 9.6.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.
- package/dist/Combobox/Combobox.d.ts +2 -0
- package/dist/Combobox/Combobox.responsive.d.ts +1 -1
- package/dist/Combobox/index.cjs +0 -2
- package/dist/Combobox/index.mjs +0 -2
- package/dist/Combobox-cjs.js +60 -9
- package/dist/Combobox-es.js +62 -11
- package/dist/ComboboxPrimitive-cjs.js +5 -2
- package/dist/ComboboxPrimitive-es.js +5 -3
- package/dist/ContentBlock/index.cjs +0 -1
- package/dist/ContentBlock/index.mjs +0 -1
- package/dist/ContentBlock-cjs.js +18 -2
- package/dist/ContentBlock-es.js +17 -1
- package/dist/docs/Combobox/Combobox.md +563 -0
- package/dist/docs/FilterPicker/FilterPicker.md +9 -11
- package/dist/docs/index.md +1 -0
- package/dist/index.cjs +0 -1
- package/dist/index.mjs +0 -1
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +1 -1
- package/package.json +2 -2
- package/dist/getMappedBreakpointWidth-cjs.js +0 -21
- package/dist/getMappedBreakpointWidth-es.js +0 -18
|
@@ -5,6 +5,7 @@ import { ComboboxClearSelection, ComboboxSelectAll, ComboboxSelectedCount } from
|
|
|
5
5
|
import { ComboboxAction, ComboboxCollection, ComboboxGroup, ComboboxGroupLabel, ComboboxItem, ComboboxList, ComboboxRow, ComboboxSeparator } from "./Combobox.items";
|
|
6
6
|
import { ComboboxPrimitive } from "../primitives/ComboboxPrimitive";
|
|
7
7
|
declare function useFilter(options?: ComboboxFilterOptions): import("@base-ui/react").AutocompleteFilter;
|
|
8
|
+
declare function useFilteredItems<Value = unknown>(): Value[];
|
|
8
9
|
declare function ComboboxRoot<Value, Multiple extends boolean | undefined = false>({ children, className, description, disabled, error, invalid, label, modal, style, multiple, readOnly, size, ...rootProps }: ComboboxRootProps<Value, Multiple>): React.JSX.Element;
|
|
9
10
|
declare function ComboboxLabel({ className, ...labelProps }: ComboboxLabelProps): React.JSX.Element;
|
|
10
11
|
declare function ComboboxLabelNote({ className, ...labelNoteProps }: ComboboxLabelNoteProps): React.JSX.Element;
|
|
@@ -63,6 +64,7 @@ declare const Combobox: typeof ComboboxRoot & {
|
|
|
63
64
|
ClearSelection: typeof ComboboxClearSelection;
|
|
64
65
|
SelectAll: typeof ComboboxSelectAll;
|
|
65
66
|
useFilter: typeof useFilter;
|
|
67
|
+
useFilteredItems: typeof useFilteredItems;
|
|
66
68
|
getSelectAllState: typeof getSelectAllState;
|
|
67
69
|
getNextSelectAllValues: typeof getNextSelectAllValues;
|
|
68
70
|
};
|
|
@@ -19,7 +19,7 @@ export declare const DEFAULT_COMBOBOX_RESPONSIVE_CONFIG: {
|
|
|
19
19
|
positionerProps: {};
|
|
20
20
|
showChipRemove: true;
|
|
21
21
|
};
|
|
22
|
-
export declare function getComboboxResponsiveConfig(
|
|
22
|
+
export declare function getComboboxResponsiveConfig(isSmallScreen: boolean): {
|
|
23
23
|
defaultModal: false;
|
|
24
24
|
footerActionButtonRecipe: {
|
|
25
25
|
size: "small";
|
package/dist/Combobox/index.cjs
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
var Combobox = require('../Combobox-cjs.js');
|
|
4
4
|
require('../tslib.es6-cjs.js');
|
|
5
5
|
require('react');
|
|
6
|
-
require('@jobber/hooks');
|
|
7
6
|
require('classnames');
|
|
8
|
-
require('../getMappedBreakpointWidth-cjs.js');
|
|
9
7
|
require('../Button-cjs.js');
|
|
10
8
|
require('react-router-dom');
|
|
11
9
|
require('../Icon-cjs.js');
|
package/dist/Combobox/index.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export { C as Combobox } from '../Combobox-es.js';
|
|
2
2
|
import '../tslib.es6-es.js';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '@jobber/hooks';
|
|
5
4
|
import 'classnames';
|
|
6
|
-
import '../getMappedBreakpointWidth-es.js';
|
|
7
5
|
import '../Button-es.js';
|
|
8
6
|
import 'react-router-dom';
|
|
9
7
|
import '../Icon-es.js';
|
package/dist/Combobox-cjs.js
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var tslib_es6 = require('./tslib.es6-cjs.js');
|
|
4
4
|
var React = require('react');
|
|
5
|
-
var jobberHooks = require('@jobber/hooks');
|
|
6
5
|
var classnames = require('classnames');
|
|
7
|
-
var getMappedBreakpointWidth = require('./getMappedBreakpointWidth-cjs.js');
|
|
8
6
|
var Button = require('./Button-cjs.js');
|
|
9
7
|
var buttonRenderAdapter = require('./buttonRenderAdapter-cjs.js');
|
|
10
8
|
var ComboboxPrimitive = require('./ComboboxPrimitive-cjs.js');
|
|
@@ -16,6 +14,56 @@ var Glimmer = require('./Glimmer-cjs.js');
|
|
|
16
14
|
var HelperText = require('./HelperText-cjs.js');
|
|
17
15
|
var FieldDescription = require('./FieldDescription-cjs.js');
|
|
18
16
|
|
|
17
|
+
function getWindowDimensions() {
|
|
18
|
+
if (!(globalThis === null || globalThis === void 0 ? void 0 : globalThis.document)) {
|
|
19
|
+
return {
|
|
20
|
+
width: undefined,
|
|
21
|
+
height: undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { innerWidth: width, innerHeight: height } = window;
|
|
25
|
+
return {
|
|
26
|
+
width,
|
|
27
|
+
height,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function useWindowDimensions() {
|
|
31
|
+
const [windowDimensions, setWindowDimensions] = React.useState(getWindowDimensions());
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
function handleResize() {
|
|
34
|
+
setWindowDimensions(getWindowDimensions());
|
|
35
|
+
}
|
|
36
|
+
window === null || window === void 0 ? void 0 : window.addEventListener("resize", handleResize);
|
|
37
|
+
return () => window === null || window === void 0 ? void 0 : window.removeEventListener("resize", handleResize);
|
|
38
|
+
}, []);
|
|
39
|
+
return windowDimensions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const BREAKPOINT_SIZES = { sm: 490, md: 768, lg: 1080, xl: 1440 };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* SSR-safe check for whether the viewport is a small ("mobile") web screen,
|
|
46
|
+
* i.e. at or below the `sm` breakpoint.
|
|
47
|
+
*
|
|
48
|
+
* Returns `false` on the server and on the client's first (hydration) render —
|
|
49
|
+
* regardless of the real viewport — so the two renders agree and React can
|
|
50
|
+
* hydrate without a mismatch. After mount an effect re-renders with the value
|
|
51
|
+
* derived from the real viewport width, and it stays in sync on resize via
|
|
52
|
+
* `useWindowDimensions`.
|
|
53
|
+
*
|
|
54
|
+
* Prefer this over reading `useWindowDimensions` directly whenever the viewport
|
|
55
|
+
* size decides which DOM is rendered (e.g. an anchored dropdown vs. a
|
|
56
|
+
* `BottomSheet`). Reading the width directly re-introduces the hydration
|
|
57
|
+
* mismatch, because the client's first render sees the real width while the
|
|
58
|
+
* server saw `undefined`.
|
|
59
|
+
*/
|
|
60
|
+
function useIsSmallScreen() {
|
|
61
|
+
const [hasMounted, setHasMounted] = React.useState(false);
|
|
62
|
+
const { width } = useWindowDimensions();
|
|
63
|
+
React.useEffect(() => setHasMounted(true), []);
|
|
64
|
+
return hasMounted && width !== undefined && width <= BREAKPOINT_SIZES.sm;
|
|
65
|
+
}
|
|
66
|
+
|
|
19
67
|
function defaultIsItemEqual(candidateValue, selectedValue) {
|
|
20
68
|
return Object.is(candidateValue, selectedValue);
|
|
21
69
|
}
|
|
@@ -75,11 +123,10 @@ const MOBILE_COMBOBOX_RESPONSIVE_CONFIG = {
|
|
|
75
123
|
},
|
|
76
124
|
showChipRemove: false,
|
|
77
125
|
};
|
|
78
|
-
function getComboboxResponsiveConfig(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return DEFAULT_COMBOBOX_RESPONSIVE_CONFIG;
|
|
126
|
+
function getComboboxResponsiveConfig(isSmallScreen) {
|
|
127
|
+
return isSmallScreen
|
|
128
|
+
? MOBILE_COMBOBOX_RESPONSIVE_CONFIG
|
|
129
|
+
: DEFAULT_COMBOBOX_RESPONSIVE_CONFIG;
|
|
83
130
|
}
|
|
84
131
|
|
|
85
132
|
const ComboboxContext = React.createContext(null);
|
|
@@ -370,6 +417,9 @@ function useFilter(options) {
|
|
|
370
417
|
const { locale } = AtlantisContext.useAtlantisContext();
|
|
371
418
|
return ComboboxPrimitive.useFilter(Object.assign({ locale }, options));
|
|
372
419
|
}
|
|
420
|
+
function useFilteredItems() {
|
|
421
|
+
return ComboboxPrimitive.useFilteredItems();
|
|
422
|
+
}
|
|
373
423
|
function getIconSize(size) {
|
|
374
424
|
if (size === "small")
|
|
375
425
|
return "small";
|
|
@@ -426,8 +476,8 @@ function getDefaultEmptyChildren({ empty, hasLoading }, emptyContent) {
|
|
|
426
476
|
}
|
|
427
477
|
function ComboboxRoot(_a) {
|
|
428
478
|
var { children, className, description, disabled, error, invalid, label, modal, style, multiple, readOnly, size = DEFAULT_SIZE } = _a, rootProps = tslib_es6.__rest(_a, ["children", "className", "description", "disabled", "error", "invalid", "label", "modal", "style", "multiple", "readOnly", "size"]);
|
|
429
|
-
const
|
|
430
|
-
const responsive = getComboboxResponsiveConfig(
|
|
479
|
+
const isSmallScreen = useIsSmallScreen();
|
|
480
|
+
const responsive = getComboboxResponsiveConfig(isSmallScreen);
|
|
431
481
|
const [positionerAnchor, setPositionerAnchor] = React.useState(null);
|
|
432
482
|
const contextValue = React.useMemo(() => ({
|
|
433
483
|
disabled: Boolean(disabled),
|
|
@@ -668,6 +718,7 @@ const Combobox = Object.assign(ComboboxRoot, {
|
|
|
668
718
|
ClearSelection: ComboboxClearSelection,
|
|
669
719
|
SelectAll: ComboboxSelectAll,
|
|
670
720
|
useFilter,
|
|
721
|
+
useFilteredItems,
|
|
671
722
|
getSelectAllState,
|
|
672
723
|
getNextSelectAllValues,
|
|
673
724
|
});
|
package/dist/Combobox-es.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { _ as __rest } from './tslib.es6-es.js';
|
|
2
|
-
import React__default from 'react';
|
|
3
|
-
import { useWindowDimensions } from '@jobber/hooks';
|
|
2
|
+
import React__default, { useState, useEffect } from 'react';
|
|
4
3
|
import classnames from 'classnames';
|
|
5
|
-
import { A as AtlantisBreakpoints } from './getMappedBreakpointWidth-es.js';
|
|
6
4
|
import { B as Button } from './Button-es.js';
|
|
7
5
|
import { n as normalizeBaseUiButtonProps } from './buttonRenderAdapter-es.js';
|
|
8
|
-
import { a as ComboboxValue, b as ComboboxClear$1, c as ComboboxList$1, d as ComboboxItem$1, e as ComboboxItemIndicator$1, f as ComboboxRow$1, g as ComboboxCollection$1, h as ComboboxGroup$1, i as ComboboxGroupLabel$1, j as ComboboxSeparator$1, k as ComboboxIcon, l as ComboboxChip, m as ComboboxChipRemove, n as ComboboxStatus, u as useFilter$1, o as
|
|
6
|
+
import { a as ComboboxValue, b as ComboboxClear$1, c as ComboboxList$1, d as ComboboxItem$1, e as ComboboxItemIndicator$1, f as ComboboxRow$1, g as ComboboxCollection$1, h as ComboboxGroup$1, i as ComboboxGroupLabel$1, j as ComboboxSeparator$1, k as ComboboxIcon, l as ComboboxChip, m as ComboboxChipRemove, n as ComboboxStatus, u as useFilter$1, o as useFilteredItems$1, p as ComboboxRoot$1, q as ComboboxInputGroup$1, r as ComboboxInput$1, s as ComboboxTrigger$1, t as ComboboxPortal, v as ComboboxBackdrop, w as ComboboxPositioner, x as ComboboxPopup, y as ComboboxChips$1, z as ComboboxEmpty$1 } from './ComboboxPrimitive-es.js';
|
|
9
7
|
import { m as mergeProps } from './useRenderElement-es.js';
|
|
10
8
|
import { I as Icon } from './Icon-es.js';
|
|
11
9
|
import { m as mergeClassName } from './mergeClassName-es.js';
|
|
@@ -14,6 +12,56 @@ import { G as Glimmer } from './Glimmer-es.js';
|
|
|
14
12
|
import { H as HelperText } from './HelperText-es.js';
|
|
15
13
|
import { F as FieldRoot, a as FieldLabel, b as FieldDescription, c as FieldError } from './FieldDescription-es.js';
|
|
16
14
|
|
|
15
|
+
function getWindowDimensions() {
|
|
16
|
+
if (!(globalThis === null || globalThis === void 0 ? void 0 : globalThis.document)) {
|
|
17
|
+
return {
|
|
18
|
+
width: undefined,
|
|
19
|
+
height: undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const { innerWidth: width, innerHeight: height } = window;
|
|
23
|
+
return {
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function useWindowDimensions() {
|
|
29
|
+
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
function handleResize() {
|
|
32
|
+
setWindowDimensions(getWindowDimensions());
|
|
33
|
+
}
|
|
34
|
+
window === null || window === void 0 ? void 0 : window.addEventListener("resize", handleResize);
|
|
35
|
+
return () => window === null || window === void 0 ? void 0 : window.removeEventListener("resize", handleResize);
|
|
36
|
+
}, []);
|
|
37
|
+
return windowDimensions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const BREAKPOINT_SIZES = { sm: 490, md: 768, lg: 1080, xl: 1440 };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SSR-safe check for whether the viewport is a small ("mobile") web screen,
|
|
44
|
+
* i.e. at or below the `sm` breakpoint.
|
|
45
|
+
*
|
|
46
|
+
* Returns `false` on the server and on the client's first (hydration) render —
|
|
47
|
+
* regardless of the real viewport — so the two renders agree and React can
|
|
48
|
+
* hydrate without a mismatch. After mount an effect re-renders with the value
|
|
49
|
+
* derived from the real viewport width, and it stays in sync on resize via
|
|
50
|
+
* `useWindowDimensions`.
|
|
51
|
+
*
|
|
52
|
+
* Prefer this over reading `useWindowDimensions` directly whenever the viewport
|
|
53
|
+
* size decides which DOM is rendered (e.g. an anchored dropdown vs. a
|
|
54
|
+
* `BottomSheet`). Reading the width directly re-introduces the hydration
|
|
55
|
+
* mismatch, because the client's first render sees the real width while the
|
|
56
|
+
* server saw `undefined`.
|
|
57
|
+
*/
|
|
58
|
+
function useIsSmallScreen() {
|
|
59
|
+
const [hasMounted, setHasMounted] = useState(false);
|
|
60
|
+
const { width } = useWindowDimensions();
|
|
61
|
+
useEffect(() => setHasMounted(true), []);
|
|
62
|
+
return hasMounted && width !== undefined && width <= BREAKPOINT_SIZES.sm;
|
|
63
|
+
}
|
|
64
|
+
|
|
17
65
|
function defaultIsItemEqual(candidateValue, selectedValue) {
|
|
18
66
|
return Object.is(candidateValue, selectedValue);
|
|
19
67
|
}
|
|
@@ -73,11 +121,10 @@ const MOBILE_COMBOBOX_RESPONSIVE_CONFIG = {
|
|
|
73
121
|
},
|
|
74
122
|
showChipRemove: false,
|
|
75
123
|
};
|
|
76
|
-
function getComboboxResponsiveConfig(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return DEFAULT_COMBOBOX_RESPONSIVE_CONFIG;
|
|
124
|
+
function getComboboxResponsiveConfig(isSmallScreen) {
|
|
125
|
+
return isSmallScreen
|
|
126
|
+
? MOBILE_COMBOBOX_RESPONSIVE_CONFIG
|
|
127
|
+
: DEFAULT_COMBOBOX_RESPONSIVE_CONFIG;
|
|
81
128
|
}
|
|
82
129
|
|
|
83
130
|
const ComboboxContext = React__default.createContext(null);
|
|
@@ -368,6 +415,9 @@ function useFilter(options) {
|
|
|
368
415
|
const { locale } = useAtlantisContext();
|
|
369
416
|
return useFilter$1(Object.assign({ locale }, options));
|
|
370
417
|
}
|
|
418
|
+
function useFilteredItems() {
|
|
419
|
+
return useFilteredItems$1();
|
|
420
|
+
}
|
|
371
421
|
function getIconSize(size) {
|
|
372
422
|
if (size === "small")
|
|
373
423
|
return "small";
|
|
@@ -424,8 +474,8 @@ function getDefaultEmptyChildren({ empty, hasLoading }, emptyContent) {
|
|
|
424
474
|
}
|
|
425
475
|
function ComboboxRoot(_a) {
|
|
426
476
|
var { children, className, description, disabled, error, invalid, label, modal, style, multiple, readOnly, size = DEFAULT_SIZE } = _a, rootProps = __rest(_a, ["children", "className", "description", "disabled", "error", "invalid", "label", "modal", "style", "multiple", "readOnly", "size"]);
|
|
427
|
-
const
|
|
428
|
-
const responsive = getComboboxResponsiveConfig(
|
|
477
|
+
const isSmallScreen = useIsSmallScreen();
|
|
478
|
+
const responsive = getComboboxResponsiveConfig(isSmallScreen);
|
|
429
479
|
const [positionerAnchor, setPositionerAnchor] = React__default.useState(null);
|
|
430
480
|
const contextValue = React__default.useMemo(() => ({
|
|
431
481
|
disabled: Boolean(disabled),
|
|
@@ -666,6 +716,7 @@ const Combobox = Object.assign(ComboboxRoot, {
|
|
|
666
716
|
ClearSelection: ComboboxClearSelection,
|
|
667
717
|
SelectAll: ComboboxSelectAll,
|
|
668
718
|
useFilter,
|
|
719
|
+
useFilteredItems,
|
|
669
720
|
getSelectAllState,
|
|
670
721
|
getNextSelectAllValues,
|
|
671
722
|
});
|
|
@@ -92,7 +92,7 @@ function ComboboxPositioner(_a) {
|
|
|
92
92
|
}
|
|
93
93
|
function ComboboxPopup(_a) {
|
|
94
94
|
var { className } = _a, popupProps = tslib_es6.__rest(_a, ["className"]);
|
|
95
|
-
return (React.createElement(ComboboxChipRemove$1.ComboboxPopup, Object.assign({}, popupProps, { className: mergeClassName.mergeClassName(styles.popup, className) })));
|
|
95
|
+
return (React.createElement(ComboboxChipRemove$1.ComboboxPopup, Object.assign({}, popupProps, { className: mergeClassName.mergeClassName(styles.popup, className), "data-elevation": "elevated" })));
|
|
96
96
|
}
|
|
97
97
|
function ComboboxArrow(_a) {
|
|
98
98
|
var { className } = _a, arrowProps = tslib_es6.__rest(_a, ["className"]);
|
|
@@ -150,7 +150,9 @@ function ComboboxSeparator(_a) {
|
|
|
150
150
|
return (React.createElement(Separator.Separator, Object.assign({}, separatorProps, { className: mergeClassName.mergeClassName(styles.separator, className) })));
|
|
151
151
|
}
|
|
152
152
|
const useFilter = ComboboxChipRemove$1.useComboboxFilter;
|
|
153
|
-
|
|
153
|
+
function useFilteredItems() {
|
|
154
|
+
return ComboboxChipRemove$1.useFilteredItems();
|
|
155
|
+
}
|
|
154
156
|
|
|
155
157
|
var ComboboxPrimitive = /*#__PURE__*/Object.freeze({
|
|
156
158
|
__proto__: null,
|
|
@@ -210,3 +212,4 @@ exports.ComboboxStatus = ComboboxStatus;
|
|
|
210
212
|
exports.ComboboxTrigger = ComboboxTrigger;
|
|
211
213
|
exports.ComboboxValue = ComboboxValue;
|
|
212
214
|
exports.useFilter = useFilter;
|
|
215
|
+
exports.useFilteredItems = useFilteredItems;
|
|
@@ -90,7 +90,7 @@ function ComboboxPositioner(_a) {
|
|
|
90
90
|
}
|
|
91
91
|
function ComboboxPopup(_a) {
|
|
92
92
|
var { className } = _a, popupProps = __rest(_a, ["className"]);
|
|
93
|
-
return (React__default.createElement(ComboboxPopup$1, Object.assign({}, popupProps, { className: mergeClassName(styles.popup, className) })));
|
|
93
|
+
return (React__default.createElement(ComboboxPopup$1, Object.assign({}, popupProps, { className: mergeClassName(styles.popup, className), "data-elevation": "elevated" })));
|
|
94
94
|
}
|
|
95
95
|
function ComboboxArrow(_a) {
|
|
96
96
|
var { className } = _a, arrowProps = __rest(_a, ["className"]);
|
|
@@ -148,7 +148,9 @@ function ComboboxSeparator(_a) {
|
|
|
148
148
|
return (React__default.createElement(Separator, Object.assign({}, separatorProps, { className: mergeClassName(styles.separator, className) })));
|
|
149
149
|
}
|
|
150
150
|
const useFilter = useComboboxFilter;
|
|
151
|
-
|
|
151
|
+
function useFilteredItems() {
|
|
152
|
+
return useFilteredItems$1();
|
|
153
|
+
}
|
|
152
154
|
|
|
153
155
|
var ComboboxPrimitive = /*#__PURE__*/Object.freeze({
|
|
154
156
|
__proto__: null,
|
|
@@ -182,4 +184,4 @@ var ComboboxPrimitive = /*#__PURE__*/Object.freeze({
|
|
|
182
184
|
useFilteredItems: useFilteredItems
|
|
183
185
|
});
|
|
184
186
|
|
|
185
|
-
export { ComboboxPrimitive as C, ComboboxValue as a, ComboboxClear as b, ComboboxList as c, ComboboxItem as d, ComboboxItemIndicator as e, ComboboxRow as f, ComboboxCollection as g, ComboboxGroup as h, ComboboxGroupLabel as i, ComboboxSeparator as j, ComboboxIcon as k, ComboboxChip as l, ComboboxChipRemove as m, ComboboxStatus as n,
|
|
187
|
+
export { ComboboxPrimitive as C, ComboboxValue as a, ComboboxClear as b, ComboboxList as c, ComboboxItem as d, ComboboxItemIndicator as e, ComboboxRow as f, ComboboxCollection as g, ComboboxGroup as h, ComboboxGroupLabel as i, ComboboxSeparator as j, ComboboxIcon as k, ComboboxChip as l, ComboboxChipRemove as m, ComboboxStatus as n, useFilteredItems as o, ComboboxRoot as p, ComboboxInputGroup as q, ComboboxInput as r, ComboboxTrigger as s, ComboboxPortal as t, useFilter as u, ComboboxBackdrop as v, ComboboxPositioner as w, ComboboxPopup as x, ComboboxChips as y, ComboboxEmpty as z };
|
package/dist/ContentBlock-cjs.js
CHANGED
|
@@ -4,12 +4,28 @@ var React = require('react');
|
|
|
4
4
|
var classnames = require('classnames');
|
|
5
5
|
var jobberHooks = require('@jobber/hooks');
|
|
6
6
|
var getMappedAtlantisSpaceToken = require('./getMappedAtlantisSpaceToken-cjs.js');
|
|
7
|
-
var getMappedBreakpointWidth = require('./getMappedBreakpointWidth-cjs.js');
|
|
8
7
|
|
|
9
8
|
var styles = {"contentBlock":"rJamQZ6fRes-","left":"bqjXV8MRO-4-","right":"sCMxIxKkFe0-","center":"_2pIQVKvVL1I-","andText":"_6dF2no3aTxw-","gutters":"_8k8YLsatGag-","spinning":"sjMwRWFdKeo-"};
|
|
10
9
|
|
|
10
|
+
const AtlantisBreakpoints = {
|
|
11
|
+
xs: 0,
|
|
12
|
+
sm: 490,
|
|
13
|
+
md: 768,
|
|
14
|
+
lg: 1080,
|
|
15
|
+
xl: 1440,
|
|
16
|
+
};
|
|
17
|
+
const getMappedBreakpointWidth = (maxWidth) => {
|
|
18
|
+
if (typeof maxWidth === "number") {
|
|
19
|
+
return `${maxWidth}px`;
|
|
20
|
+
}
|
|
21
|
+
if (AtlantisBreakpoints[maxWidth]) {
|
|
22
|
+
return (AtlantisBreakpoints[maxWidth] + "px");
|
|
23
|
+
}
|
|
24
|
+
return maxWidth;
|
|
25
|
+
};
|
|
26
|
+
|
|
11
27
|
function ContentBlock({ children, maxWidth = jobberHooks.Breakpoints.smaller, andText, gutters, justify = "left", as: Tag = "div", dataAttributes, ariaAttributes, role, id, UNSAFE_className, UNSAFE_style, }) {
|
|
12
|
-
return (React.createElement(Tag, Object.assign({ role: role, id: id }, dataAttributes, ariaAttributes, { style: Object.assign({ "--content-block-max-width": getMappedBreakpointWidth
|
|
28
|
+
return (React.createElement(Tag, Object.assign({ role: role, id: id }, dataAttributes, ariaAttributes, { style: Object.assign({ "--content-block-max-width": getMappedBreakpointWidth(maxWidth), "--content-block-gutters": getMappedAtlantisSpaceToken.getMappedAtlantisSpaceToken(gutters) }, UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.container), className: classnames(styles.contentBlock, andText && styles.andText, gutters && styles.gutters, justify === "left" && styles.left, justify === "right" && styles.right, justify === "center" && styles.center, UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.container) }), children));
|
|
13
29
|
}
|
|
14
30
|
|
|
15
31
|
exports.ContentBlock = ContentBlock;
|
package/dist/ContentBlock-es.js
CHANGED
|
@@ -2,10 +2,26 @@ import React__default from 'react';
|
|
|
2
2
|
import classnames from 'classnames';
|
|
3
3
|
import { Breakpoints } from '@jobber/hooks';
|
|
4
4
|
import { g as getMappedAtlantisSpaceToken } from './getMappedAtlantisSpaceToken-es.js';
|
|
5
|
-
import { g as getMappedBreakpointWidth } from './getMappedBreakpointWidth-es.js';
|
|
6
5
|
|
|
7
6
|
var styles = {"contentBlock":"rJamQZ6fRes-","left":"bqjXV8MRO-4-","right":"sCMxIxKkFe0-","center":"_2pIQVKvVL1I-","andText":"_6dF2no3aTxw-","gutters":"_8k8YLsatGag-","spinning":"sjMwRWFdKeo-"};
|
|
8
7
|
|
|
8
|
+
const AtlantisBreakpoints = {
|
|
9
|
+
xs: 0,
|
|
10
|
+
sm: 490,
|
|
11
|
+
md: 768,
|
|
12
|
+
lg: 1080,
|
|
13
|
+
xl: 1440,
|
|
14
|
+
};
|
|
15
|
+
const getMappedBreakpointWidth = (maxWidth) => {
|
|
16
|
+
if (typeof maxWidth === "number") {
|
|
17
|
+
return `${maxWidth}px`;
|
|
18
|
+
}
|
|
19
|
+
if (AtlantisBreakpoints[maxWidth]) {
|
|
20
|
+
return (AtlantisBreakpoints[maxWidth] + "px");
|
|
21
|
+
}
|
|
22
|
+
return maxWidth;
|
|
23
|
+
};
|
|
24
|
+
|
|
9
25
|
function ContentBlock({ children, maxWidth = Breakpoints.smaller, andText, gutters, justify = "left", as: Tag = "div", dataAttributes, ariaAttributes, role, id, UNSAFE_className, UNSAFE_style, }) {
|
|
10
26
|
return (React__default.createElement(Tag, Object.assign({ role: role, id: id }, dataAttributes, ariaAttributes, { style: Object.assign({ "--content-block-max-width": getMappedBreakpointWidth(maxWidth), "--content-block-gutters": getMappedAtlantisSpaceToken(gutters) }, UNSAFE_style === null || UNSAFE_style === void 0 ? void 0 : UNSAFE_style.container), className: classnames(styles.contentBlock, andText && styles.andText, gutters && styles.gutters, justify === "left" && styles.left, justify === "right" && styles.right, justify === "center" && styles.center, UNSAFE_className === null || UNSAFE_className === void 0 ? void 0 : UNSAFE_className.container) }), children));
|
|
11
27
|
}
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Combobox
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Combobox lets a user search and select one or more values from a list. Use it
|
|
6
|
+
when the list benefits from filtering, when the selected values need to appear
|
|
7
|
+
in the field, or when the popup needs supporting actions such as creating a new
|
|
8
|
+
option.
|
|
9
|
+
|
|
10
|
+
Use Combobox when the selected value is saved, submitted, assigned, or otherwise
|
|
11
|
+
becomes part of the user's work.
|
|
12
|
+
|
|
13
|
+
## Anatomy
|
|
14
|
+
|
|
15
|
+
| Part | Description |
|
|
16
|
+
| ----------- | ----------------------------------------------------------------- |
|
|
17
|
+
| Field | Label, optional note, description, and error treatment |
|
|
18
|
+
| Trigger | Visible field that opens the popup and displays the current value |
|
|
19
|
+
| Search | Input used to filter options, either in the trigger or popup |
|
|
20
|
+
| Content | Popup or mobile sheet that contains options and affordances |
|
|
21
|
+
| Item | Selectable option with optional prefix, description, and suffix |
|
|
22
|
+
| Empty state | Message shown when search has no matching options |
|
|
23
|
+
| Actions | Sticky action rows below the option list |
|
|
24
|
+
| Footer | Sticky region for multiple-selection affordances |
|
|
25
|
+
|
|
26
|
+
## Behavior
|
|
27
|
+
|
|
28
|
+
#### Opening and closing
|
|
29
|
+
|
|
30
|
+
Combobox opens when the trigger is clicked, tapped, or activated by keyboard. It
|
|
31
|
+
closes when the user selects a single-select option, clicks or taps outside the
|
|
32
|
+
popup, or presses Esc.
|
|
33
|
+
|
|
34
|
+
Multiple-selection comboboxes stay open while values are selected so users can
|
|
35
|
+
choose more than one option.
|
|
36
|
+
|
|
37
|
+
#### Search
|
|
38
|
+
|
|
39
|
+
Search filters the option list as the user types. Search can happen directly in
|
|
40
|
+
the trigger or inside the popup, depending on the trigger pattern.
|
|
41
|
+
|
|
42
|
+
#### Empty state
|
|
43
|
+
|
|
44
|
+
When no options match, the empty state appears above actions and footer content.
|
|
45
|
+
This keeps creation actions and multiple-selection controls in predictable
|
|
46
|
+
locations.
|
|
47
|
+
|
|
48
|
+
#### Actions and footer
|
|
49
|
+
|
|
50
|
+
Actions are anchored below the option list. The multiple-selection footer is
|
|
51
|
+
anchored below options and actions when present.
|
|
52
|
+
|
|
53
|
+
#### Small screens
|
|
54
|
+
|
|
55
|
+
On small screens, Combobox automatically presents the popup as a bottom-anchored
|
|
56
|
+
sheet. Consumers use the same Combobox API across desktop and mobile web.
|
|
57
|
+
|
|
58
|
+
## Trigger patterns
|
|
59
|
+
|
|
60
|
+
Use `Combobox.TriggerInput` when the user searches directly in the field.
|
|
61
|
+
|
|
62
|
+
Use `Combobox.TriggerValue` when the closed field should behave more like a
|
|
63
|
+
Select and search belongs inside the popup.
|
|
64
|
+
|
|
65
|
+
Use `Combobox.TriggerMultipleValue` for multi-select fields that show selected
|
|
66
|
+
chips in the closed field and keep search inside the popup.
|
|
67
|
+
|
|
68
|
+
## Variants
|
|
69
|
+
|
|
70
|
+
#### Single select
|
|
71
|
+
|
|
72
|
+
Use single select when the user can choose one value.
|
|
73
|
+
|
|
74
|
+
#### Multiple select
|
|
75
|
+
|
|
76
|
+
Use multiple select when the user can choose several values and needs selected
|
|
77
|
+
values to remain visible in the field.
|
|
78
|
+
|
|
79
|
+
#### Popup search
|
|
80
|
+
|
|
81
|
+
Use popup search when the trigger should display the current value and search
|
|
82
|
+
should happen after the popup opens.
|
|
83
|
+
|
|
84
|
+
## Item content
|
|
85
|
+
|
|
86
|
+
Items can be plain text for the default row treatment, or composed with
|
|
87
|
+
`Combobox.ItemPrefix`, `Combobox.ItemLabel`, `Combobox.ItemDescription`, and
|
|
88
|
+
`Combobox.ItemSuffix` for richer option content. The selected checkmark is
|
|
89
|
+
included by default.
|
|
90
|
+
|
|
91
|
+
## Empty and loading states
|
|
92
|
+
|
|
93
|
+
Combobox includes a default empty state so search results always resolve in the
|
|
94
|
+
right place, above actions and footer content. Customize the message through
|
|
95
|
+
`Combobox.Content`, render your own `Combobox.Empty`, or opt out when the
|
|
96
|
+
experience needs to own that region.
|
|
97
|
+
|
|
98
|
+
Loading uses four glimmer rows by default. The row count and loading content can
|
|
99
|
+
be customized when a product needs a different loading treatment.
|
|
100
|
+
|
|
101
|
+
## Actions
|
|
102
|
+
|
|
103
|
+
Actions are for work outside option selection, such as creating or inviting an
|
|
104
|
+
item. The default action row uses the leading add icon shown in Figma, while
|
|
105
|
+
`Combobox.ActionPrefix` and `Combobox.ActionLabel` are available when the row
|
|
106
|
+
needs custom content.
|
|
107
|
+
|
|
108
|
+
## Multiple-selection footer
|
|
109
|
+
|
|
110
|
+
Use `Combobox.SelectionFooter` for the opinionated multiple-select footer. It
|
|
111
|
+
keeps selected count, clear, and select-all controls in a consistent order.
|
|
112
|
+
|
|
113
|
+
Select-all behavior is product-owned: the app decides whether "all" means
|
|
114
|
+
visible, filtered, loaded, paginated, or server-known results.
|
|
115
|
+
|
|
116
|
+
## Content Guidelines
|
|
117
|
+
|
|
118
|
+
#### Labels
|
|
119
|
+
|
|
120
|
+
Use a short noun label that names the value being selected.
|
|
121
|
+
|
|
122
|
+
| ✅ Do | ❌ Don't |
|
|
123
|
+
| ------------ | -------------------------------- |
|
|
124
|
+
| Team member | Choose which team member to use |
|
|
125
|
+
| Job type | Select one of these job types |
|
|
126
|
+
| Service area | Search and pick a service region |
|
|
127
|
+
|
|
128
|
+
#### Search placeholder
|
|
129
|
+
|
|
130
|
+
Use placeholder text that describes what can be searched. Keep it short.
|
|
131
|
+
|
|
132
|
+
| ✅ Do | ❌ Don't |
|
|
133
|
+
| ------------------- | ----------------------------------------- |
|
|
134
|
+
| Search team members | Start typing to search all team members |
|
|
135
|
+
| Search clients | Enter the name of the client to find them |
|
|
136
|
+
| Search job types | Filter the dropdown |
|
|
137
|
+
|
|
138
|
+
#### Item text
|
|
139
|
+
|
|
140
|
+
Use the item label for the selectable value. Use descriptions only when they
|
|
141
|
+
help distinguish similar options.
|
|
142
|
+
|
|
143
|
+
| ✅ Do | ❌ Don't |
|
|
144
|
+
| ---------------------------- | ---------------------------------------- |
|
|
145
|
+
| Ryan Clearwater | Select Ryan Clearwater |
|
|
146
|
+
| Ryan Clearwater + Sales lead | Ryan Clearwater, who is the sales lead |
|
|
147
|
+
| Downtown + Service area | Downtown service area available for jobs |
|
|
148
|
+
|
|
149
|
+
#### Actions
|
|
150
|
+
|
|
151
|
+
Use actions for work outside option selection, such as creating or inviting an
|
|
152
|
+
item. Action labels should be verb-first and sentence-cased.
|
|
153
|
+
|
|
154
|
+
| ✅ Do | ❌ Don't |
|
|
155
|
+
| ------------------ | ------------------------ |
|
|
156
|
+
| Create team member | Team member creation |
|
|
157
|
+
| Invite team member | Send invitation to staff |
|
|
158
|
+
| Add client | Client |
|
|
159
|
+
|
|
160
|
+
## Do's and Don'ts
|
|
161
|
+
|
|
162
|
+
#### Do
|
|
163
|
+
|
|
164
|
+
* ✅ Use Combobox when the list benefits from search or filtering
|
|
165
|
+
* ✅ Use enough item context to distinguish similar values
|
|
166
|
+
* ✅ Keep empty state above actions and footer controls
|
|
167
|
+
* ✅ Use the multiple-selection footer for clear and select-all affordances
|
|
168
|
+
* ✅ Define what "select all" means before including it
|
|
169
|
+
|
|
170
|
+
#### Don't
|
|
171
|
+
|
|
172
|
+
* ❌ Don't use Combobox for a tiny static list where Select is simpler
|
|
173
|
+
* ❌ Don't hide empty state behind actions or footer controls
|
|
174
|
+
* ❌ Don't assume "select all" means every server result
|
|
175
|
+
* ❌ Don't mix unrelated actions into the popup
|
|
176
|
+
* ❌ Don't overload item rows with content that slows scanning
|
|
177
|
+
|
|
178
|
+
## Rich item data
|
|
179
|
+
|
|
180
|
+
Options can include more than a primary label when extra context helps users
|
|
181
|
+
choose confidently. Use descriptions, prefixes, or suffixes when similar options
|
|
182
|
+
need to be distinguished at a glance. Those fields can also be made filterable,
|
|
183
|
+
so search can match the details users naturally type.
|
|
184
|
+
|
|
185
|
+
## Accessibility
|
|
186
|
+
|
|
187
|
+
Combobox should have a clear accessible label. Keyboard behavior and active item
|
|
188
|
+
management are provided by Base UI.
|
|
189
|
+
|
|
190
|
+
#### Keyboard navigation
|
|
191
|
+
|
|
192
|
+
| Key | Behavior |
|
|
193
|
+
| ------------------ | ------------------------------------------------------ |
|
|
194
|
+
| Tab | Moves focus to the trigger, actions, or footer buttons |
|
|
195
|
+
| Up and Down arrows | Moves through available options |
|
|
196
|
+
| Enter | Selects the highlighted option or activates an action |
|
|
197
|
+
| Esc | Closes the popup |
|
|
198
|
+
|
|
199
|
+
## Related components
|
|
200
|
+
|
|
201
|
+
* To choose one value from a small static list, use [Select](../Select/Select.md)
|
|
202
|
+
* To present actions that do not filter or select form values, use
|
|
203
|
+
[Menu](../Menu/Menu.md)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## Composition
|
|
207
|
+
|
|
208
|
+
Combobox is a composable field built on `ComboboxPrimitive`. The sugared
|
|
209
|
+
component provides common recipes while preserving access to lower-level pieces
|
|
210
|
+
when a product flow needs more control.
|
|
211
|
+
|
|
212
|
+
### Trigger recipes
|
|
213
|
+
|
|
214
|
+
Most implementations should start with one of the trigger recipes.
|
|
215
|
+
|
|
216
|
+
#### `Combobox.TriggerInput`
|
|
217
|
+
|
|
218
|
+
Use `Combobox.TriggerInput` when the user should search directly in the field.
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<Combobox label="Team member" items={teamMembers}>
|
|
222
|
+
<Combobox.TriggerInput placeholder="Search team members" />
|
|
223
|
+
<Combobox.Content>
|
|
224
|
+
<Combobox.List>
|
|
225
|
+
{member => (
|
|
226
|
+
<Combobox.Item key={member.id} value={member}>
|
|
227
|
+
{member.name}
|
|
228
|
+
</Combobox.Item>
|
|
229
|
+
)}
|
|
230
|
+
</Combobox.List>
|
|
231
|
+
</Combobox.Content>
|
|
232
|
+
</Combobox>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### `Combobox.TriggerValue`
|
|
236
|
+
|
|
237
|
+
Use `Combobox.TriggerValue` when the closed field should display the selected
|
|
238
|
+
value and the search input should live inside the popup.
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
<Combobox label="Team member" items={teamMembers}>
|
|
242
|
+
<Combobox.TriggerValue placeholder="Select team member" />
|
|
243
|
+
<Combobox.Content>
|
|
244
|
+
<Combobox.Input placeholder="Search team members" />
|
|
245
|
+
<Combobox.List>
|
|
246
|
+
{member => (
|
|
247
|
+
<Combobox.Item key={member.id} value={member}>
|
|
248
|
+
{member.name}
|
|
249
|
+
</Combobox.Item>
|
|
250
|
+
)}
|
|
251
|
+
</Combobox.List>
|
|
252
|
+
</Combobox.Content>
|
|
253
|
+
</Combobox>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `Combobox.TriggerMultipleValue`
|
|
257
|
+
|
|
258
|
+
Use `Combobox.TriggerMultipleValue` when the closed field should display
|
|
259
|
+
selected chips and the search input should live inside the popup.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<Combobox multiple label="Assigned team" items={teamMembers}>
|
|
263
|
+
<Combobox.TriggerMultipleValue getItemLabel={member => member.name} />
|
|
264
|
+
<Combobox.Content>
|
|
265
|
+
<Combobox.Input placeholder="Search team members" />
|
|
266
|
+
<Combobox.List>
|
|
267
|
+
{member => (
|
|
268
|
+
<Combobox.Item key={member.id} value={member}>
|
|
269
|
+
{member.name}
|
|
270
|
+
</Combobox.Item>
|
|
271
|
+
)}
|
|
272
|
+
</Combobox.List>
|
|
273
|
+
</Combobox.Content>
|
|
274
|
+
</Combobox>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### `Combobox.TriggerMultipleWithInput`
|
|
278
|
+
|
|
279
|
+
Use `Combobox.TriggerMultipleWithInput` when multi-select chips and search
|
|
280
|
+
should both live in the trigger.
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
<Combobox multiple label="Assigned team" items={teamMembers}>
|
|
284
|
+
<Combobox.TriggerMultipleWithInput getItemLabel={member => member.name} />
|
|
285
|
+
<Combobox.Content>
|
|
286
|
+
<Combobox.List>
|
|
287
|
+
{member => (
|
|
288
|
+
<Combobox.Item key={member.id} value={member}>
|
|
289
|
+
{member.name}
|
|
290
|
+
</Combobox.Item>
|
|
291
|
+
)}
|
|
292
|
+
</Combobox.List>
|
|
293
|
+
</Combobox.Content>
|
|
294
|
+
</Combobox>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Field text
|
|
298
|
+
|
|
299
|
+
Pass `label`, `description`, and `error` on `Combobox` for the default Atlantis
|
|
300
|
+
field treatment. If the layout needs different placement, compose
|
|
301
|
+
`Combobox.Label`, `Combobox.Description`, and `Combobox.Error` manually.
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
<Combobox
|
|
305
|
+
label="Team member"
|
|
306
|
+
description="Search by name"
|
|
307
|
+
error="Choose a team member"
|
|
308
|
+
items={teamMembers}
|
|
309
|
+
>
|
|
310
|
+
{/* trigger and content */}
|
|
311
|
+
</Combobox>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Empty state
|
|
315
|
+
|
|
316
|
+
`Combobox.Content` renders `"No results found"` by default when search has no
|
|
317
|
+
matches. The empty state is placed after options and before actions or footer
|
|
318
|
+
content.
|
|
319
|
+
|
|
320
|
+
Use the `empty` prop to customize the default message:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
<Combobox.Content empty="No team members found">
|
|
324
|
+
{/* options */}
|
|
325
|
+
</Combobox.Content>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
When the empty state needs custom markup, omit the automatic empty state and
|
|
329
|
+
render `Combobox.Empty` directly:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
<Combobox.Content empty={null}>
|
|
333
|
+
<Combobox.List>{/* options */}</Combobox.List>
|
|
334
|
+
<Combobox.Empty>No team members match this search.</Combobox.Empty>
|
|
335
|
+
</Combobox.Content>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Actions
|
|
339
|
+
|
|
340
|
+
Use `Combobox.Action` for interactive rows that are not selectable options, such
|
|
341
|
+
as creating a new customer or inviting a team member. Use `Combobox.Item` when
|
|
342
|
+
the row represents a value the Combobox can select.
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
<Combobox.Content empty={`No matches for "${query}"`}>
|
|
346
|
+
<Combobox.List>{/* options */}</Combobox.List>
|
|
347
|
+
<Combobox.Actions>
|
|
348
|
+
<Combobox.Action onClick={() => createTeamMember(query)}>
|
|
349
|
+
Create team member
|
|
350
|
+
</Combobox.Action>
|
|
351
|
+
</Combobox.Actions>
|
|
352
|
+
</Combobox.Content>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Clicking an action runs the action's handler and leaves the popup open. If an
|
|
356
|
+
action should close the popup, control `open` from product state and set it to
|
|
357
|
+
`false` in the action handler. If the action opens another overlay, such as a
|
|
358
|
+
Dialog, also coordinate that from product state so focus and closing behavior
|
|
359
|
+
match the product flow.
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
const [open, setOpen] = useState(false);
|
|
363
|
+
|
|
364
|
+
<Combobox open={open} onOpenChange={setOpen}>
|
|
365
|
+
<Combobox.TriggerInput />
|
|
366
|
+
<Combobox.Content>
|
|
367
|
+
<Combobox.List>{/* options */}</Combobox.List>
|
|
368
|
+
<Combobox.Actions>
|
|
369
|
+
<Combobox.Action
|
|
370
|
+
onClick={() => {
|
|
371
|
+
createTeamMember();
|
|
372
|
+
setOpen(false);
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
Create team member
|
|
376
|
+
</Combobox.Action>
|
|
377
|
+
</Combobox.Actions>
|
|
378
|
+
</Combobox.Content>
|
|
379
|
+
</Combobox>;
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Footer and select all
|
|
383
|
+
|
|
384
|
+
`Combobox.SelectionFooter` is the default multiple-select footer. It owns the
|
|
385
|
+
visual ordering for selected count, clear, and select all. Use
|
|
386
|
+
`showSelectedCount={false}` when the count should be omitted.
|
|
387
|
+
|
|
388
|
+
Select-all behavior is intentionally not automatic. Products must decide the
|
|
389
|
+
candidate set and pass `onSelectAll`.
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
const selectAllState = Combobox.getSelectAllState({
|
|
393
|
+
selectedValues,
|
|
394
|
+
candidateValues: visibleValues,
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The candidate set can be visible results, filtered results, loaded results, the
|
|
399
|
+
current page, or another product-defined set.
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
const [selectedMembers, setSelectedMembers] = useState([]);
|
|
403
|
+
const selectAllState = Combobox.getSelectAllState({
|
|
404
|
+
selectedValues: selectedMembers,
|
|
405
|
+
candidateValues: visibleMembers,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
<Combobox
|
|
409
|
+
multiple
|
|
410
|
+
value={selectedMembers}
|
|
411
|
+
onValueChange={setSelectedMembers}
|
|
412
|
+
items={visibleMembers}
|
|
413
|
+
>
|
|
414
|
+
<Combobox.TriggerMultipleValue getItemLabel={member => member.name} />
|
|
415
|
+
<Combobox.Content>
|
|
416
|
+
<Combobox.Input placeholder="Search team members" />
|
|
417
|
+
<Combobox.List>{/* options */}</Combobox.List>
|
|
418
|
+
<Combobox.SelectionFooter
|
|
419
|
+
selectAllState={selectAllState}
|
|
420
|
+
onSelectAll={() =>
|
|
421
|
+
setSelectedMembers(
|
|
422
|
+
Combobox.getNextSelectAllValues({
|
|
423
|
+
selectedValues: selectedMembers,
|
|
424
|
+
candidateValues: visibleMembers,
|
|
425
|
+
})
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
/>
|
|
429
|
+
</Combobox.Content>
|
|
430
|
+
</Combobox>;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Controlled and uncontrolled usage
|
|
434
|
+
|
|
435
|
+
Combobox follows Base UI's value model. Use `value` and `onValueChange` when the
|
|
436
|
+
app owns selection state, or `defaultValue` for uncontrolled selection.
|
|
437
|
+
|
|
438
|
+
Single-select values are one item or `null`. Multi-select values are arrays.
|
|
439
|
+
When values are objects, use `itemToString` for display and `itemToValue` for
|
|
440
|
+
form submission.
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
<Combobox
|
|
444
|
+
value={selectedMember}
|
|
445
|
+
onValueChange={setSelectedMember}
|
|
446
|
+
itemToString={member => member?.name ?? ""}
|
|
447
|
+
itemToValue={member => member.id}
|
|
448
|
+
items={teamMembers}
|
|
449
|
+
>
|
|
450
|
+
{/* trigger and content */}
|
|
451
|
+
</Combobox>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Filtering and data strategy
|
|
455
|
+
|
|
456
|
+
For local data, pass `items` and optionally customize the root `filter` prop.
|
|
457
|
+
`Combobox.useFilter` exposes Base UI's filter helper with Atlantis locale wired
|
|
458
|
+
in, so matching handles locale-sensitive text consistently.
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
const filter = Combobox.useFilter();
|
|
462
|
+
|
|
463
|
+
<Combobox
|
|
464
|
+
items={teamMembers}
|
|
465
|
+
filter={(member, query) =>
|
|
466
|
+
filter.contains(member.name, query) ||
|
|
467
|
+
filter.contains(member.department, query) ||
|
|
468
|
+
filter.contains(member.role, query)
|
|
469
|
+
}
|
|
470
|
+
>
|
|
471
|
+
{/* trigger and content */}
|
|
472
|
+
</Combobox>;
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
`Combobox.useFilteredItems` exposes Base UI's filtered item collection for
|
|
476
|
+
custom list rendering, virtualization, or pagination affordances. It does not
|
|
477
|
+
load additional data on its own; it only reads the filtered items currently
|
|
478
|
+
known to Combobox.
|
|
479
|
+
|
|
480
|
+
Combobox does not own pagination, but it gives products the pieces to build it.
|
|
481
|
+
Pass the currently loaded items into `items`, load additional pages from product
|
|
482
|
+
state or an API call, and compose loading UI inside the popup with
|
|
483
|
+
`Combobox.Status` and `ActivityIndicator` when needed. For local large lists,
|
|
484
|
+
use `Combobox.useFilteredItems` when custom rendering or virtualization needs
|
|
485
|
+
access to the current filtered collection.
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
<Combobox.Content>
|
|
489
|
+
<Combobox.Input placeholder="Search team members" />
|
|
490
|
+
<Combobox.List>{/* loaded options */}</Combobox.List>
|
|
491
|
+
{isLoadingMore && (
|
|
492
|
+
<Combobox.Status>
|
|
493
|
+
<ActivityIndicator aria-label="Loading more team members" />
|
|
494
|
+
</Combobox.Status>
|
|
495
|
+
)}
|
|
496
|
+
</Combobox.Content>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
For async, paginated, virtualized, or server-filtered data, keep the data
|
|
500
|
+
strategy in product code. Fetch from input changes, pass the current loaded or
|
|
501
|
+
windowed items to Combobox, and disable client filtering when the server already
|
|
502
|
+
returned filtered results.
|
|
503
|
+
|
|
504
|
+
> **NOTICE:** Combobox does not infer total result sets, page boundaries, or select-all
|
|
505
|
+
> semantics for server-backed data.
|
|
506
|
+
|
|
507
|
+
## Mobile web
|
|
508
|
+
|
|
509
|
+
Combobox automatically switches to a bottom-anchored sheet at the Atlantis small
|
|
510
|
+
breakpoint. This uses Base UI's Combobox modal and backdrop behavior, not
|
|
511
|
+
Atlantis `BottomSheet`, so consumers do not need a separate mobile API.
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
## Props
|
|
515
|
+
|
|
516
|
+
### Web
|
|
517
|
+
|
|
518
|
+
| Prop | Type | Required | Default | Description |
|
|
519
|
+
|------|------|----------|---------|-------------|
|
|
520
|
+
| `actionsRef` | `RefObject<Actions>` | No | — | A ref to imperative actions. - `unmount`: Manually unmounts the combobox. Call this after any externally controlled c... |
|
|
521
|
+
| `autoComplete` | `string` | No | — | Provides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attribu... |
|
|
522
|
+
| `autoHighlight` | `boolean` | No | `false` | Whether the first matching item is highlighted automatically while filtering. |
|
|
523
|
+
| `className` | `string` | No | — | |
|
|
524
|
+
| `defaultInputValue` | `string | number | readonly string[]` | No | — | The uncontrolled input value when initially rendered. To render a controlled input, use the `inputValue` prop instead. |
|
|
525
|
+
| `defaultOpen` | `boolean` | No | `false` | Whether the popup is initially open. To render a controlled popup, use the `open` prop instead. |
|
|
526
|
+
| `defaultValue` | `ComboboxValueType<Value, Multiple>` | No | — | The uncontrolled selected value of the combobox when it's initially rendered. To render a controlled combobox, use t... |
|
|
527
|
+
| `description` | `string` | No | — | |
|
|
528
|
+
| `disabled` | `boolean` | No | `false` | Whether the component should ignore user interaction. |
|
|
529
|
+
| `error` | `string` | No | — | |
|
|
530
|
+
| `filter` | `(itemValue: Value, query: string, itemToString?: (itemValue: Value) => string) => boolean` | No | — | Filter function used to match items vs input query. |
|
|
531
|
+
| `filteredItems` | `readonly any[] | readonly Group<any>[]` | No | — | Filtered items to display in the list. When provided, the list will use these items instead of filtering the `items` ... |
|
|
532
|
+
| `form` | `string` | No | — | Identifies the form that owns the internal input. Useful when the combobox is rendered outside the form. |
|
|
533
|
+
| `grid` | `boolean` | No | `false` | Whether list items are presented in a grid layout. When enabled, arrow keys navigate across rows and columns inferred... |
|
|
534
|
+
| `highlightItemOnHover` | `boolean` | No | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differenti... |
|
|
535
|
+
| `id` | `string` | No | — | The id of the component. |
|
|
536
|
+
| `inline` | `boolean` | No | `false` | Whether the list is rendered inline without using the component's own popup. Specify `open` unconditionally in conju... |
|
|
537
|
+
| `inputRef` | `Ref<HTMLInputElement>` | No | — | A ref to the hidden input element. |
|
|
538
|
+
| `inputValue` | `string | number | readonly string[]` | No | — | The input value of the combobox. Use when controlled. |
|
|
539
|
+
| `invalid` | `boolean` | No | — | |
|
|
540
|
+
| `isItemEqualToValue` | `(itemValue: Value, value: Value) => boolean` | No | — | Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when it... |
|
|
541
|
+
| `items` | `readonly any[] | readonly Group<any>[]` | No | — | The items to be displayed in the list. Can be either a flat array of items or an array of groups with items. |
|
|
542
|
+
| `itemToStringLabel` | `(itemValue: Value) => string` | No | — | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a str... |
|
|
543
|
+
| `itemToStringValue` | `(itemValue: Value) => string` | No | — | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a str... |
|
|
544
|
+
| `label` | `ReactNode` | No | — | |
|
|
545
|
+
| `limit` | `number` | No | `-1` | The maximum number of items to display in the list. |
|
|
546
|
+
| `locale` | `LocalesArgument` | No | — | The locale to use for string comparison. Defaults to the user's runtime locale. |
|
|
547
|
+
| `loopFocus` | `boolean` | No | `true` | Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The ... |
|
|
548
|
+
| `modal` | `boolean` | No | `false` | Determines if the popup enters a modal state when open. - `true`: user interaction is limited to the popup: document ... |
|
|
549
|
+
| `multiple` | `boolean` | No | `false` | Whether multiple items can be selected. |
|
|
550
|
+
| `name` | `string` | No | — | Identifies the field when a form is submitted. |
|
|
551
|
+
| `onInputValueChange` | `(inputValue: string, eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the input value changes. |
|
|
552
|
+
| `onItemHighlighted` | `(highlightedValue: Value, eventDetails: HighlightEventDetails) => void` | No | — | Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item value (or `undefined` if n... |
|
|
553
|
+
| `onOpenChange` | `(open: boolean, eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the popup is opened or closed. |
|
|
554
|
+
| `onOpenChangeComplete` | `(open: boolean) => void` | No | — | Event handler called after any animations complete when the popup is opened or closed. |
|
|
555
|
+
| `onValueChange` | `(value: ComboboxValueType<Value, Multiple> | (Multiple extends true ? never : null), eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the selected value of the combobox changes. |
|
|
556
|
+
| `open` | `boolean` | No | — | Whether the popup is currently open. Use when controlled. |
|
|
557
|
+
| `openOnInputClick` | `boolean` | No | `true` | Whether the popup opens when clicking the input. |
|
|
558
|
+
| `readOnly` | `boolean` | No | `false` | Whether the user should be unable to choose a different option from the popup. |
|
|
559
|
+
| `required` | `boolean` | No | `false` | Whether the user must choose a value before submitting a form. |
|
|
560
|
+
| `size` | `ComboboxSize` | No | — | |
|
|
561
|
+
| `style` | `CSSProperties` | No | — | |
|
|
562
|
+
| `value` | `ComboboxValueType<Value, Multiple>` | No | — | The selected value of the combobox. Use when controlled. |
|
|
563
|
+
| `virtualized` | `boolean` | No | `false` | Whether the items are being externally virtualized. |
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# FilterPicker
|
|
2
2
|
|
|
3
|
-
> **NOTICE:** This component was previously named `Combobox`. Use `FilterPicker`
|
|
4
|
-
>
|
|
3
|
+
> **NOTICE:** This component was previously named `Combobox`. Use `FilterPicker` for
|
|
4
|
+
> filtering existing content. For searchable field selection, use the new Base
|
|
5
|
+
> UI-backed [Combobox](../Combobox/Combobox.md).
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
variety of scenarios, such as selecting items from a predefined list, filtering
|
|
9
|
-
and searching through data.
|
|
7
|
+
FilterPicker lets users narrow an existing list, table, report, or page of
|
|
8
|
+
records. Use it when selected options act as filters for nearby content.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
If the user is choosing a value to save, submit, assign, or display in a form
|
|
11
|
+
field, use [Combobox](../Combobox/Combobox.md) instead.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
and searching within a list of items.
|
|
13
|
+
## Design & usage guidelines
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
with the added benefit of filtering.
|
|
15
|
+
FilterPicker's primary function is to apply filter criteria to existing content.
|
|
18
16
|
|
|
19
17
|
The FilterPicker also has the flexibility to allow for custom actions, such as
|
|
20
18
|
adding a new item to the list of options or managing a selection.
|
package/dist/docs/index.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
[choosing-components](./choosing-components/choosing-components.md)
|
|
16
16
|
[Cluster](./Cluster/Cluster.md)
|
|
17
17
|
[Colors](./Colors/Colors.md)
|
|
18
|
+
[Combobox](./Combobox/Combobox.md)
|
|
18
19
|
[ConfirmationModal](./ConfirmationModal/ConfirmationModal.md)
|
|
19
20
|
[Container](./Container/Container.md)
|
|
20
21
|
[Content](./Content/Content.md)
|
package/dist/index.cjs
CHANGED
|
@@ -115,7 +115,6 @@ require('color');
|
|
|
115
115
|
require('./tslib.es6-cjs.js');
|
|
116
116
|
require('react-router-dom');
|
|
117
117
|
require('./getMappedAtlantisSpaceToken-cjs.js');
|
|
118
|
-
require('./getMappedBreakpointWidth-cjs.js');
|
|
119
118
|
require('./buttonRenderAdapter-cjs.js');
|
|
120
119
|
require('./useRenderElement-cjs.js');
|
|
121
120
|
require('./ComboboxPrimitive-cjs.js');
|
package/dist/index.mjs
CHANGED
|
@@ -113,7 +113,6 @@ import 'color';
|
|
|
113
113
|
import './tslib.es6-es.js';
|
|
114
114
|
import 'react-router-dom';
|
|
115
115
|
import './getMappedAtlantisSpaceToken-es.js';
|
|
116
|
-
import './getMappedBreakpointWidth-es.js';
|
|
117
116
|
import './buttonRenderAdapter-es.js';
|
|
118
117
|
import './useRenderElement-es.js';
|
|
119
118
|
import './ComboboxPrimitive-es.js';
|
|
@@ -28,5 +28,5 @@ declare function ComboboxEmpty({ className, ...emptyProps }: ComboboxPrimitiveEm
|
|
|
28
28
|
declare function ComboboxClear({ className, render, ...clearProps }: ComboboxPrimitiveClearProps): React.JSX.Element;
|
|
29
29
|
declare function ComboboxSeparator({ className, ...separatorProps }: ComboboxPrimitiveSeparatorProps): React.JSX.Element;
|
|
30
30
|
declare const useFilter: typeof BaseCombobox.useFilter;
|
|
31
|
-
declare
|
|
31
|
+
declare function useFilteredItems<Value = unknown>(): Value[];
|
|
32
32
|
export { ComboboxRoot as Root, ComboboxLabel as Label, ComboboxValue as Value, ComboboxInput as Input, ComboboxInputGroup as InputGroup, ComboboxTrigger as Trigger, ComboboxList as List, ComboboxStatus as Status, ComboboxPortal as Portal, ComboboxBackdrop as Backdrop, ComboboxPositioner as Positioner, ComboboxPopup as Popup, ComboboxArrow as Arrow, ComboboxIcon as Icon, ComboboxGroup as Group, ComboboxGroupLabel as GroupLabel, ComboboxItem as Item, ComboboxItemIndicator as ItemIndicator, ComboboxChips as Chips, ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxRow as Row, ComboboxCollection as Collection, ComboboxEmpty as Empty, ComboboxClear as Clear, ComboboxSeparator as Separator, useFilter, useFilteredItems, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -549,5 +549,5 @@
|
|
|
549
549
|
"> 1%",
|
|
550
550
|
"IE 10"
|
|
551
551
|
],
|
|
552
|
-
"gitHead": "
|
|
552
|
+
"gitHead": "2ea35aba9020c49cabbece42af65fb007281284e"
|
|
553
553
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const AtlantisBreakpoints = {
|
|
4
|
-
xs: 0,
|
|
5
|
-
sm: 490,
|
|
6
|
-
md: 768,
|
|
7
|
-
lg: 1080,
|
|
8
|
-
xl: 1440,
|
|
9
|
-
};
|
|
10
|
-
const getMappedBreakpointWidth = (maxWidth) => {
|
|
11
|
-
if (typeof maxWidth === "number") {
|
|
12
|
-
return `${maxWidth}px`;
|
|
13
|
-
}
|
|
14
|
-
if (AtlantisBreakpoints[maxWidth]) {
|
|
15
|
-
return (AtlantisBreakpoints[maxWidth] + "px");
|
|
16
|
-
}
|
|
17
|
-
return maxWidth;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
exports.AtlantisBreakpoints = AtlantisBreakpoints;
|
|
21
|
-
exports.getMappedBreakpointWidth = getMappedBreakpointWidth;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const AtlantisBreakpoints = {
|
|
2
|
-
xs: 0,
|
|
3
|
-
sm: 490,
|
|
4
|
-
md: 768,
|
|
5
|
-
lg: 1080,
|
|
6
|
-
xl: 1440,
|
|
7
|
-
};
|
|
8
|
-
const getMappedBreakpointWidth = (maxWidth) => {
|
|
9
|
-
if (typeof maxWidth === "number") {
|
|
10
|
-
return `${maxWidth}px`;
|
|
11
|
-
}
|
|
12
|
-
if (AtlantisBreakpoints[maxWidth]) {
|
|
13
|
-
return (AtlantisBreakpoints[maxWidth] + "px");
|
|
14
|
-
}
|
|
15
|
-
return maxWidth;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export { AtlantisBreakpoints as A, getMappedBreakpointWidth as g };
|