@box/blueprint-web 12.114.0 → 12.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib-esm/grid-list-v2/grid-list-v2.d.ts +5 -0
- package/dist/lib-esm/grid-list-v2/grid-list-v2.js +21 -0
- package/dist/lib-esm/grid-list-v2/index.d.ts +11 -0
- package/dist/lib-esm/grid-list-v2/index.js +17 -0
- package/dist/lib-esm/grid-list-v2/types.d.ts +12 -0
- package/dist/lib-esm/index.css +390 -101
- package/dist/lib-esm/index.d.ts +1 -0
- package/dist/lib-esm/index.js +1 -0
- package/dist/lib-esm/types/file-category.d.ts +11 -0
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item-actions.js +19 -5
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item-header.js +5 -2
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item-subtitle.js +5 -2
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item-thumbnail.js +106 -4
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item.js +58 -6
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list-item.module.js +1 -1
- package/dist/lib-esm/util-components/base-grid-list-item/base-grid-list.js +12 -2
- package/dist/lib-esm/util-components/base-grid-list-item/index.d.ts +1 -1
- package/dist/lib-esm/util-components/base-grid-list-item/types.d.ts +91 -3
- package/package.json +3 -3
package/dist/lib-esm/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './empty-state';
|
|
|
25
25
|
export * from './focusable';
|
|
26
26
|
export * from './ghost';
|
|
27
27
|
export * from './grid-list-item';
|
|
28
|
+
export * from './grid-list-v2';
|
|
28
29
|
export * from './guided-tooltip';
|
|
29
30
|
export * from './icon-dual-state-button';
|
|
30
31
|
export * from './icon-toggle-button';
|
package/dist/lib-esm/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export { EmptyState } from './empty-state/empty-state.js';
|
|
|
32
32
|
export { Focusable } from './focusable/focusable.js';
|
|
33
33
|
export { Ghost } from './ghost/ghost.js';
|
|
34
34
|
export { GridList } from './grid-list-item/index.js';
|
|
35
|
+
export { GridListV2 } from './grid-list-v2/index.js';
|
|
35
36
|
export { GuidedTooltip } from './guided-tooltip/guided-tooltip.js';
|
|
36
37
|
export { GuidedTooltipContext } from './guided-tooltip/utils/guided-tooltip-context.js';
|
|
37
38
|
export { IconDualStateButton } from './icon-dual-state-button/icon-dual-state-button.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File category type for categorizing files by their type/format.
|
|
3
|
+
*
|
|
4
|
+
* These categories match those returned by `getFileIconType` from `@box/item-icon`.
|
|
5
|
+
* Due to circular dependency constraints, `@box/blueprint-web` cannot directly import
|
|
6
|
+
* `@box/item-icon`, so consumers should use `getFileIconType` in their own code to
|
|
7
|
+
* map file extensions to categories.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link https://git.dev.box.net/Box/frontend-mono/tree/master/packages/group-shared-features/item-icon | @box/item-icon}
|
|
10
|
+
*/
|
|
11
|
+
export type FileCategory = 'audio' | 'boxcanvas' | 'boxnote' | 'code' | 'document' | 'docuworks-binder' | 'docuworks-file' | 'dwg' | 'excel-spreadsheet' | 'google-docs' | 'google-sheets' | 'google-slides' | 'illustrator' | 'image' | 'indesign' | 'keynote' | 'numbers' | 'pages' | 'pdf' | 'photoshop' | 'powerpoint-presentation' | 'presentation' | 'spreadsheet' | 'text' | 'threed' | 'vector' | 'video' | 'word-document' | 'zip';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { Pin } from '@box/blueprint-web-assets/icons/Fill';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { forwardRef, useCallback, useRef } from 'react';
|
|
4
|
+
import { forwardRef, useCallback, useRef, useEffect } from 'react';
|
|
5
5
|
import { mergeProps } from 'react-aria';
|
|
6
6
|
import { TooltipTrigger } from 'react-aria-components';
|
|
7
7
|
import { IconButton } from '../../primitives/icon-button/icon-button.js';
|
|
@@ -37,14 +37,23 @@ const BaseListActions = ({
|
|
|
37
37
|
const GridListActions = ({
|
|
38
38
|
children,
|
|
39
39
|
selectionEnabled,
|
|
40
|
-
isSelectionCheckboxDisabled
|
|
40
|
+
isSelectionCheckboxDisabled,
|
|
41
|
+
centerText,
|
|
42
|
+
setHasActions
|
|
41
43
|
}) => {
|
|
44
|
+
const hasChildren = children !== null && children !== undefined && children !== false;
|
|
45
|
+
// Communicate action button presence to parent context for subtitle visibility logic
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setHasActions(hasChildren);
|
|
48
|
+
}, [hasChildren, setHasActions]);
|
|
42
49
|
return jsxs(Fragment, {
|
|
43
50
|
children: [selectionEnabled && jsx(ListCheckbox, {
|
|
44
51
|
className: styles.selection,
|
|
45
52
|
isDisabled: isSelectionCheckboxDisabled
|
|
46
53
|
}), jsx("div", {
|
|
47
|
-
className: styles.inner,
|
|
54
|
+
className: clsx(styles.inner, {
|
|
55
|
+
[styles.innerCentered]: centerText
|
|
56
|
+
}),
|
|
48
57
|
children: children
|
|
49
58
|
})]
|
|
50
59
|
});
|
|
@@ -90,7 +99,8 @@ const BaseGridListActionIconButton = /*#__PURE__*/forwardRef(function BaseGridLi
|
|
|
90
99
|
const BaseGridListActions = /*#__PURE__*/forwardRef(function BaseGridListActions(props, forwardedRef) {
|
|
91
100
|
const {
|
|
92
101
|
layoutStyle,
|
|
93
|
-
selectionMode
|
|
102
|
+
selectionMode,
|
|
103
|
+
centerText
|
|
94
104
|
} = useBaseGridListContext();
|
|
95
105
|
const {
|
|
96
106
|
children,
|
|
@@ -102,7 +112,8 @@ const BaseGridListActions = /*#__PURE__*/forwardRef(function BaseGridListActions
|
|
|
102
112
|
loading,
|
|
103
113
|
pinned,
|
|
104
114
|
pinAriaLabel,
|
|
105
|
-
setIsItemInteracted
|
|
115
|
+
setIsItemInteracted,
|
|
116
|
+
setHasActions
|
|
106
117
|
} = useBaseGridListItemContext();
|
|
107
118
|
const divRef = useRef(null);
|
|
108
119
|
const forkRef = useForkRef(divRef, forwardedRef);
|
|
@@ -110,6 +121,7 @@ const BaseGridListActions = /*#__PURE__*/forwardRef(function BaseGridListActions
|
|
|
110
121
|
return null;
|
|
111
122
|
}
|
|
112
123
|
const isList = layoutStyle === 'list' || layoutStyle === 'small-list';
|
|
124
|
+
const isGridV2 = layoutStyle === 'grid-v2';
|
|
113
125
|
const isSelectionEnabled = selectionMode === 'multiple';
|
|
114
126
|
const isRenderPropUsed = typeof children === 'function';
|
|
115
127
|
return jsxs(Fragment, {
|
|
@@ -122,8 +134,10 @@ const BaseGridListActions = /*#__PURE__*/forwardRef(function BaseGridListActions
|
|
|
122
134
|
selectionEnabled: isSelectionEnabled,
|
|
123
135
|
children: isRenderPropUsed ? children(setIsItemInteracted) : children
|
|
124
136
|
}) : jsx(GridListActions, {
|
|
137
|
+
centerText: isGridV2 && centerText,
|
|
125
138
|
isSelectionCheckboxDisabled: isSelectionCheckboxDisabled,
|
|
126
139
|
selectionEnabled: isSelectionEnabled,
|
|
140
|
+
setHasActions: setHasActions,
|
|
127
141
|
children: isRenderPropUsed ? children(setIsItemInteracted) : children
|
|
128
142
|
})
|
|
129
143
|
}), pinned && jsx(Status, {
|
|
@@ -20,7 +20,8 @@ const BaseGridListHeader = /*#__PURE__*/forwardRef(function BaseGridListHeader(p
|
|
|
20
20
|
loading
|
|
21
21
|
} = useBaseGridListItemContext();
|
|
22
22
|
const {
|
|
23
|
-
layoutStyle
|
|
23
|
+
layoutStyle,
|
|
24
|
+
centerText
|
|
24
25
|
} = useBaseGridListContext();
|
|
25
26
|
const {
|
|
26
27
|
Wrapper,
|
|
@@ -33,6 +34,7 @@ const BaseGridListHeader = /*#__PURE__*/forwardRef(function BaseGridListHeader(p
|
|
|
33
34
|
});
|
|
34
35
|
const isLargeItem = layoutStyle === 'list';
|
|
35
36
|
const isSmallListItem = layoutStyle === 'small-list';
|
|
37
|
+
const isGridV2 = layoutStyle === 'grid-v2';
|
|
36
38
|
return (
|
|
37
39
|
// @ts-expect-error Fix this type error
|
|
38
40
|
jsx(Wrapper, {
|
|
@@ -41,7 +43,8 @@ const BaseGridListHeader = /*#__PURE__*/forwardRef(function BaseGridListHeader(p
|
|
|
41
43
|
...rest,
|
|
42
44
|
ref: useForkRef(ref, forwardedRef),
|
|
43
45
|
className: clsx(styles.header, {
|
|
44
|
-
[styles.loading]: loading
|
|
46
|
+
[styles.loading]: loading,
|
|
47
|
+
[styles.textCentered]: isGridV2 && centerText
|
|
45
48
|
}, className),
|
|
46
49
|
children: loading ? jsx(Ghost, {
|
|
47
50
|
borderRadius: isSmallListItem ? '2rem' : undefined,
|
|
@@ -19,7 +19,8 @@ const BaseGridListSubtitle = /*#__PURE__*/forwardRef(function BaseGridListSubtit
|
|
|
19
19
|
loading
|
|
20
20
|
} = useBaseGridListItemContext();
|
|
21
21
|
const {
|
|
22
|
-
layoutStyle
|
|
22
|
+
layoutStyle,
|
|
23
|
+
centerText
|
|
23
24
|
} = useBaseGridListContext();
|
|
24
25
|
const {
|
|
25
26
|
Wrapper,
|
|
@@ -30,6 +31,7 @@ const BaseGridListSubtitle = /*#__PURE__*/forwardRef(function BaseGridListSubtit
|
|
|
30
31
|
skipOverflowCheck: loading
|
|
31
32
|
});
|
|
32
33
|
const isGridItem = layoutStyle === 'grid';
|
|
34
|
+
const isGridV2 = layoutStyle === 'grid-v2';
|
|
33
35
|
const isSmallListItem = layoutStyle === 'small-list';
|
|
34
36
|
const forkRef = useForkRef(ref, forwardedRef);
|
|
35
37
|
if (isSmallListItem && loading) {
|
|
@@ -44,7 +46,8 @@ const BaseGridListSubtitle = /*#__PURE__*/forwardRef(function BaseGridListSubtit
|
|
|
44
46
|
...rest,
|
|
45
47
|
ref: forkRef,
|
|
46
48
|
className: clsx(styles.subtitle, {
|
|
47
|
-
[styles.loading]: loading
|
|
49
|
+
[styles.loading]: loading,
|
|
50
|
+
[styles.textCentered]: isGridV2 && centerText
|
|
48
51
|
}, className),
|
|
49
52
|
children: loading ? jsx(Ghost, {
|
|
50
53
|
variant: "rectangle",
|
|
@@ -1,21 +1,108 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { Ghost } from '../../ghost/ghost.js';
|
|
5
|
+
import { Text } from '../../text/text.js';
|
|
5
6
|
import { useBaseGridListItemContext } from './base-grid-list-item.js';
|
|
6
7
|
import styles from './base-grid-list-item.module.js';
|
|
7
8
|
|
|
9
|
+
// Color tokens grouped by visual color to minimize bundle size
|
|
10
|
+
// Each token uses Blueprint variable with legacy fallback
|
|
11
|
+
const C = {
|
|
12
|
+
// Watermelon Red 120 (#BE2C46) - pdf, powerpoint-presentation, code
|
|
13
|
+
red: 'var(--bp-watermelon-red-120, var(--watermelon-red-120))',
|
|
14
|
+
// Orange 130 (#A95A12) - boxcanvas, threed, vector, pages, google-slides, illustrator, presentation
|
|
15
|
+
orange: 'var(--bp-orange-130, var(--orange-120))',
|
|
16
|
+
// Green Light 135 (#197E54) - image, excel-spreadsheet, numbers, google-sheets, spreadsheet
|
|
17
|
+
green: 'var(--bp-green-light-135, var(--green-light-120))',
|
|
18
|
+
// Light Blue 115 (#1F72D6) - photoshop, dwg, keynote, video
|
|
19
|
+
lightBlue: 'var(--bp-light-blue-115, var(--light-blue-110))',
|
|
20
|
+
// Light Blue 135 (#1757A4) - word-document, google-docs, text
|
|
21
|
+
darkBlue: 'var(--bp-light-blue-135, var(--light-blue-120))',
|
|
22
|
+
// Purple Rain 100 (#9F3FED) - audio
|
|
23
|
+
purple: 'var(--bp-purple-rain-100, var(--purple-rain-100))',
|
|
24
|
+
// Text on Light (#222222) - default for unspecified categories
|
|
25
|
+
gray: 'var(--bp-gray-100, var(--gray-100))'
|
|
26
|
+
};
|
|
27
|
+
/** Color mapping for file categories, optimized for bundle size */
|
|
28
|
+
const FILE_CATEGORY_COLORS = {
|
|
29
|
+
// Purple Rain 100
|
|
30
|
+
audio: C.purple,
|
|
31
|
+
// Orange 130
|
|
32
|
+
boxcanvas: C.orange,
|
|
33
|
+
// Default (text-on-light)
|
|
34
|
+
boxnote: C.gray,
|
|
35
|
+
// Watermelon Red 120
|
|
36
|
+
code: C.red,
|
|
37
|
+
// Default (text-on-light)
|
|
38
|
+
document: C.gray,
|
|
39
|
+
// Default (text-on-light)
|
|
40
|
+
'docuworks-binder': C.gray,
|
|
41
|
+
// Green Light 135
|
|
42
|
+
'docuworks-file': C.green,
|
|
43
|
+
// Light Blue 115
|
|
44
|
+
dwg: C.lightBlue,
|
|
45
|
+
// Green Light 135
|
|
46
|
+
'excel-spreadsheet': C.green,
|
|
47
|
+
// Light Blue 135
|
|
48
|
+
'google-docs': C.darkBlue,
|
|
49
|
+
// Green Light 135
|
|
50
|
+
'google-sheets': C.green,
|
|
51
|
+
// Orange 130
|
|
52
|
+
'google-slides': C.orange,
|
|
53
|
+
// Orange 130
|
|
54
|
+
illustrator: C.orange,
|
|
55
|
+
// Green Light 135
|
|
56
|
+
image: C.green,
|
|
57
|
+
// Default (text-on-light)
|
|
58
|
+
indesign: C.gray,
|
|
59
|
+
// Light Blue 115
|
|
60
|
+
keynote: C.lightBlue,
|
|
61
|
+
// Green Light 135
|
|
62
|
+
numbers: C.green,
|
|
63
|
+
// Orange 130
|
|
64
|
+
pages: C.orange,
|
|
65
|
+
// Watermelon Red 120
|
|
66
|
+
pdf: C.red,
|
|
67
|
+
// Light Blue 115
|
|
68
|
+
photoshop: C.lightBlue,
|
|
69
|
+
// Watermelon Red 120
|
|
70
|
+
'powerpoint-presentation': C.red,
|
|
71
|
+
// Orange 130
|
|
72
|
+
presentation: C.orange,
|
|
73
|
+
// Green Light 135
|
|
74
|
+
spreadsheet: C.green,
|
|
75
|
+
// Light Blue 135
|
|
76
|
+
text: C.darkBlue,
|
|
77
|
+
// Orange 130
|
|
78
|
+
threed: C.orange,
|
|
79
|
+
// Orange 130
|
|
80
|
+
vector: C.orange,
|
|
81
|
+
// Light Blue 115
|
|
82
|
+
video: C.lightBlue,
|
|
83
|
+
// Light Blue 135
|
|
84
|
+
'word-document': C.darkBlue,
|
|
85
|
+
// Default (text-on-light)
|
|
86
|
+
zip: C.gray
|
|
87
|
+
};
|
|
8
88
|
const BaseGridListThumbnail = /*#__PURE__*/forwardRef(function BaseGridListThumbnail(props, forwardedRef) {
|
|
9
89
|
const {
|
|
10
90
|
children,
|
|
11
91
|
className,
|
|
12
92
|
hasCustomSize,
|
|
93
|
+
fileExtension,
|
|
94
|
+
fileCategory,
|
|
95
|
+
statusBadge,
|
|
13
96
|
...rest
|
|
14
97
|
} = props;
|
|
15
98
|
const {
|
|
16
99
|
loading
|
|
17
100
|
} = useBaseGridListItemContext();
|
|
18
|
-
|
|
101
|
+
// Normalize file extension to uppercase for display (e.g., "pdf" → "PDF")
|
|
102
|
+
const normalizedExtension = fileExtension?.toUpperCase();
|
|
103
|
+
const extensionColor = fileCategory ? FILE_CATEGORY_COLORS[fileCategory] : undefined;
|
|
104
|
+
const hasBadges = !loading && (statusBadge || normalizedExtension);
|
|
105
|
+
return jsxs("div", {
|
|
19
106
|
...rest,
|
|
20
107
|
ref: forwardedRef,
|
|
21
108
|
className: clsx(styles.thumbnail, {
|
|
@@ -23,10 +110,25 @@ const BaseGridListThumbnail = /*#__PURE__*/forwardRef(function BaseGridListThumb
|
|
|
23
110
|
}, {
|
|
24
111
|
[styles.thumbnailContent]: !hasCustomSize
|
|
25
112
|
}, className),
|
|
26
|
-
children: loading ? jsx(Ghost, {
|
|
113
|
+
children: [loading ? jsx(Ghost, {
|
|
27
114
|
borderRadius: "0.5rem",
|
|
28
115
|
height: "100%"
|
|
29
|
-
}) : children
|
|
116
|
+
}) : children, hasBadges && jsxs("div", {
|
|
117
|
+
className: styles.thumbnailBadges,
|
|
118
|
+
children: [statusBadge && jsx("span", {
|
|
119
|
+
className: styles.statusBadge,
|
|
120
|
+
children: statusBadge
|
|
121
|
+
}), normalizedExtension && jsx(Text, {
|
|
122
|
+
as: "span",
|
|
123
|
+
className: styles.fileTypePill,
|
|
124
|
+
"data-file-type": normalizedExtension,
|
|
125
|
+
style: extensionColor ? {
|
|
126
|
+
color: extensionColor
|
|
127
|
+
} : undefined,
|
|
128
|
+
variant: "caption",
|
|
129
|
+
children: normalizedExtension
|
|
130
|
+
})]
|
|
131
|
+
})]
|
|
30
132
|
});
|
|
31
133
|
});
|
|
32
134
|
|
|
@@ -4,20 +4,28 @@ import noop from 'lodash/noop';
|
|
|
4
4
|
import { forwardRef, useState, useMemo, createContext, useContext } from 'react';
|
|
5
5
|
import { GridListItem } from 'react-aria-components';
|
|
6
6
|
import { useDebounce } from '../../utils/use-debounce.js';
|
|
7
|
-
import { StaticGridListItem } from './static-grid-list-item.js';
|
|
8
7
|
import { useBaseGridListContext } from './base-grid-list.js';
|
|
9
8
|
import styles from './base-grid-list-item.module.js';
|
|
9
|
+
import { StaticGridListItem } from './static-grid-list-item.js';
|
|
10
10
|
|
|
11
11
|
const BaseGridListItemContext = /*#__PURE__*/createContext({
|
|
12
12
|
loading: false,
|
|
13
13
|
pinned: false,
|
|
14
14
|
pinAriaLabel: undefined,
|
|
15
15
|
setIsItemInteracted: noop,
|
|
16
|
-
isItemInteracted: false
|
|
16
|
+
isItemInteracted: false,
|
|
17
|
+
hasActions: false,
|
|
18
|
+
setHasActions: noop
|
|
17
19
|
});
|
|
18
20
|
const useBaseGridListItemContext = () => {
|
|
19
21
|
return useContext(BaseGridListItemContext);
|
|
20
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* CSS selectors for grid-v2 click handling (defined outside component to save bundle size).
|
|
25
|
+
* Combined into single strings to reduce .closest() calls.
|
|
26
|
+
*/
|
|
27
|
+
const CHECKBOX_SELECTOR = '[class*="selection"], input[type="checkbox"]';
|
|
28
|
+
const INTERACTIVE_SELECTOR = 'button, a, input, [role="button"], [class*="actions"]';
|
|
21
29
|
const BaseGridListItem = /*#__PURE__*/forwardRef(function BaseGridListItem(props, forwardedRef) {
|
|
22
30
|
const {
|
|
23
31
|
children,
|
|
@@ -29,10 +37,12 @@ const BaseGridListItem = /*#__PURE__*/forwardRef(function BaseGridListItem(props
|
|
|
29
37
|
} = props;
|
|
30
38
|
const {
|
|
31
39
|
layoutStyle,
|
|
32
|
-
isInteractive
|
|
40
|
+
isInteractive,
|
|
41
|
+
onAction
|
|
33
42
|
} = useBaseGridListContext();
|
|
34
43
|
const textValue = typeof rest.textValue === 'string' ? rest.textValue : undefined;
|
|
35
44
|
const [isItemInteracted, setIsItemInteracted] = useState(false);
|
|
45
|
+
const [hasActions, setHasActions] = useState(false);
|
|
36
46
|
// Use debounce with a small delay to make sure there is style flashing finishing interaction (e.g. closing dropdown)
|
|
37
47
|
const debouncedIsItemInteracted = useDebounce(isItemInteracted, 50);
|
|
38
48
|
const context = useMemo(() => ({
|
|
@@ -40,8 +50,10 @@ const BaseGridListItem = /*#__PURE__*/forwardRef(function BaseGridListItem(props
|
|
|
40
50
|
pinned,
|
|
41
51
|
pinAriaLabel,
|
|
42
52
|
isItemInteracted,
|
|
43
|
-
setIsItemInteracted
|
|
44
|
-
|
|
53
|
+
setIsItemInteracted,
|
|
54
|
+
hasActions,
|
|
55
|
+
setHasActions
|
|
56
|
+
}), [loading, pinned, pinAriaLabel, isItemInteracted, hasActions]);
|
|
45
57
|
let layoutStyleClass;
|
|
46
58
|
switch (layoutStyle) {
|
|
47
59
|
case 'list':
|
|
@@ -50,10 +62,42 @@ const BaseGridListItem = /*#__PURE__*/forwardRef(function BaseGridListItem(props
|
|
|
50
62
|
case 'grid':
|
|
51
63
|
layoutStyleClass = styles.gridListItem;
|
|
52
64
|
break;
|
|
65
|
+
case 'grid-v2':
|
|
66
|
+
layoutStyleClass = styles.gridListV2Item;
|
|
67
|
+
break;
|
|
53
68
|
case 'small-list':
|
|
54
69
|
layoutStyleClass = styles.smallListItem;
|
|
55
70
|
}
|
|
56
71
|
const GridListItem$1 = isInteractive ? GridListItem : StaticGridListItem;
|
|
72
|
+
const isGridV2 = layoutStyle === 'grid-v2';
|
|
73
|
+
/**
|
|
74
|
+
* Grid-v2 click behavior fix:
|
|
75
|
+
*
|
|
76
|
+
* WHY V2 NEEDS THIS BUT V1 DOESN'T:
|
|
77
|
+
* V1's CSS uses minmax() for grid rows, so content fills most of the clickable area.
|
|
78
|
+
* V2 has explicit padding creating empty space that triggers unwanted selection. This padding is needed to make space for
|
|
79
|
+
* the seclection and focus outline states which uses psuedo elements to create the outline.
|
|
80
|
+
*
|
|
81
|
+
* WHAT THIS DOES:
|
|
82
|
+
* - Checkbox clicks: propagate → toggle selection
|
|
83
|
+
* - Action buttons: propagate → button handlers fire
|
|
84
|
+
* - Content/empty space: stop propagation, trigger onAction
|
|
85
|
+
*/
|
|
86
|
+
const handleContentPointerDown = e => {
|
|
87
|
+
const target = e.target;
|
|
88
|
+
// Allow checkbox and interactive element clicks to propagate normally
|
|
89
|
+
if (target.closest(CHECKBOX_SELECTOR) || target.closest(INTERACTIVE_SELECTOR)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Stop propagation for content clicks, then trigger onAction
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
const itemKey = rest.id ?? textValue;
|
|
95
|
+
if (onAction && itemKey) {
|
|
96
|
+
onAction(itemKey);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
// Only apply pointer handler for grid-v2 in interactive mode
|
|
100
|
+
const pointerDownHandler = isGridV2 && isInteractive ? handleContentPointerDown : undefined;
|
|
57
101
|
return jsx(GridListItem$1, {
|
|
58
102
|
...rest,
|
|
59
103
|
ref: forwardedRef,
|
|
@@ -61,11 +105,19 @@ const BaseGridListItem = /*#__PURE__*/forwardRef(function BaseGridListItem(props
|
|
|
61
105
|
[styles.loading]: loading
|
|
62
106
|
}, {
|
|
63
107
|
[styles.isItemInteracted]: isItemInteracted || debouncedIsItemInteracted
|
|
108
|
+
},
|
|
109
|
+
// In grid-v2, keep subtitle visible on hover/focus when there are no action buttons
|
|
110
|
+
{
|
|
111
|
+
[styles.noActions]: isGridV2 && !hasActions
|
|
64
112
|
}, className),
|
|
65
113
|
textValue: textValue,
|
|
66
114
|
children: jsx(BaseGridListItemContext.Provider, {
|
|
67
115
|
value: context,
|
|
68
|
-
children:
|
|
116
|
+
children: pointerDownHandler ? jsx("div", {
|
|
117
|
+
className: styles.gridV2ClickShield,
|
|
118
|
+
onPointerDownCapture: pointerDownHandler,
|
|
119
|
+
children: children
|
|
120
|
+
}) : children
|
|
69
121
|
})
|
|
70
122
|
});
|
|
71
123
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../../index.css';
|
|
2
|
-
var styles = {"smallList":"bp_base_grid_list_item_module_smallList--
|
|
2
|
+
var styles = {"smallList":"bp_base_grid_list_item_module_smallList--33914","smallListItem":"bp_base_grid_list_item_module_smallListItem--33914","header":"bp_base_grid_list_item_module_header--33914","subtitle":"bp_base_grid_list_item_module_subtitle--33914","loading":"bp_base_grid_list_item_module_loading--33914","thumbnail":"bp_base_grid_list_item_module_thumbnail--33914","thumbnailContent":"bp_base_grid_list_item_module_thumbnailContent--33914","actions":"bp_base_grid_list_item_module_actions--33914","selection":"bp_base_grid_list_item_module_selection--33914","inner":"bp_base_grid_list_item_module_inner--33914","actionsCheckboxWrapper":"bp_base_grid_list_item_module_actionsCheckboxWrapper--33914","largeList":"bp_base_grid_list_item_module_largeList--33914","largeListItem":"bp_base_grid_list_item_module_largeListItem--33914","fade":"bp_base_grid_list_item_module_fade--33914","description":"bp_base_grid_list_item_module_description--33914","snippet":"bp_base_grid_list_item_module_snippet--33914","snippetContent":"bp_base_grid_list_item_module_snippetContent--33914","gridList":"bp_base_grid_list_item_module_gridList--33914","gridListItem":"bp_base_grid_list_item_module_gridListItem--33914","statusPin":"bp_base_grid_list_item_module_statusPin--33914","isItemInteracted":"bp_base_grid_list_item_module_isItemInteracted--33914","tooltipContent":"bp_base_grid_list_item_module_tooltipContent--33914","tooltipArrow":"bp_base_grid_list_item_module_tooltipArrow--33914","gridListV2":"bp_base_grid_list_item_module_gridListV2--33914","gridListV2Item":"bp_base_grid_list_item_module_gridListV2Item--33914","noActions":"bp_base_grid_list_item_module_noActions--33914","thumbnailBadges":"bp_base_grid_list_item_module_thumbnailBadges--33914","statusBadge":"bp_base_grid_list_item_module_statusBadge--33914","fileTypePill":"bp_base_grid_list_item_module_fileTypePill--33914","textCentered":"bp_base_grid_list_item_module_textCentered--33914","innerCentered":"bp_base_grid_list_item_module_innerCentered--33914","gridListV2Small":"bp_base_grid_list_item_module_gridListV2Small--33914","gridV2ClickShield":"bp_base_grid_list_item_module_gridV2ClickShield--33914","staticList":"bp_base_grid_list_item_module_staticList--33914","staticListItem":"bp_base_grid_list_item_module_staticListItem--33914"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
|
@@ -29,6 +29,8 @@ const BaseGridList = /*#__PURE__*/forwardRef(function BaseGridList(props, forwar
|
|
|
29
29
|
className,
|
|
30
30
|
isInteractive = true,
|
|
31
31
|
layoutStyle,
|
|
32
|
+
centerText = false,
|
|
33
|
+
variant = 'large',
|
|
32
34
|
...rest
|
|
33
35
|
} = props;
|
|
34
36
|
const {
|
|
@@ -40,8 +42,10 @@ const BaseGridList = /*#__PURE__*/forwardRef(function BaseGridList(props, forwar
|
|
|
40
42
|
selectionBehavior: rest.selectionBehavior,
|
|
41
43
|
layoutStyle,
|
|
42
44
|
isInteractive,
|
|
45
|
+
centerText,
|
|
46
|
+
variant,
|
|
43
47
|
onAction: handleOnAction
|
|
44
|
-
}), [rest.selectionMode, rest.selectionBehavior, layoutStyle, isInteractive, handleOnAction]);
|
|
48
|
+
}), [rest.selectionMode, rest.selectionBehavior, layoutStyle, isInteractive, centerText, variant, handleOnAction]);
|
|
45
49
|
let layoutStyleClass;
|
|
46
50
|
let layoutFinal;
|
|
47
51
|
switch (layoutStyle) {
|
|
@@ -53,12 +57,18 @@ const BaseGridList = /*#__PURE__*/forwardRef(function BaseGridList(props, forwar
|
|
|
53
57
|
layoutStyleClass = styles.gridList;
|
|
54
58
|
layoutFinal = 'grid';
|
|
55
59
|
break;
|
|
60
|
+
case 'grid-v2':
|
|
61
|
+
layoutStyleClass = styles.gridListV2;
|
|
62
|
+
layoutFinal = 'grid';
|
|
63
|
+
break;
|
|
56
64
|
case 'small-list':
|
|
57
65
|
layoutFinal = 'stack';
|
|
58
66
|
layoutStyleClass = styles.smallList;
|
|
59
67
|
break;
|
|
60
68
|
}
|
|
61
69
|
const GridList$1 = isInteractive ? GridList : StaticGridList;
|
|
70
|
+
const isGridV2 = layoutStyle === 'grid-v2';
|
|
71
|
+
const variantClass = isGridV2 && variant === 'small' ? styles.gridListV2Small : undefined;
|
|
62
72
|
return jsx("div", {
|
|
63
73
|
onKeyDownCapture: stopPropagationForNonSpecialKeys,
|
|
64
74
|
children: jsx(BaseGridListContext.Provider, {
|
|
@@ -66,7 +76,7 @@ const BaseGridList = /*#__PURE__*/forwardRef(function BaseGridList(props, forwar
|
|
|
66
76
|
children: jsx(GridList$1, {
|
|
67
77
|
...rest,
|
|
68
78
|
ref: forwardedRef,
|
|
69
|
-
className: clsx(layoutStyleClass, className),
|
|
79
|
+
className: clsx(layoutStyleClass, variantClass, className),
|
|
70
80
|
"data-modern": enableModernizedComponents ? 'true' : 'false',
|
|
71
81
|
layout: layoutFinal,
|
|
72
82
|
onAction: handleOnAction,
|
|
@@ -33,4 +33,4 @@ export declare const BaseGridList: import("react").ForwardRefExoticComponent<imp
|
|
|
33
33
|
*/
|
|
34
34
|
ActionIconButton: import("react").ForwardRefExoticComponent<(Omit<import("../../primitives/icon-button/types").IconButtonVariantsProps, "ref"> | Omit<import("../../primitives/icon-button/types").IconButtonSmallUtilityVariantProps, "ref">) & import("react").RefAttributes<HTMLButtonElement>>;
|
|
35
35
|
};
|
|
36
|
-
export type { BaseGridListActionIconButtonProps, BaseGridListActionsProps, BaseGridListDescriptionProps, BaseGridListHeaderProps, BaseGridListItemProps, BaseGridListProps, BaseGridListSubtitleProps, BaseGridListThumbnailProps, } from './types';
|
|
36
|
+
export type { BaseGridListActionIconButtonProps, BaseGridListActionsProps, BaseGridListDescriptionProps, BaseGridListHeaderProps, BaseGridListItemProps, BaseGridListProps, BaseGridListSubtitleProps, BaseGridListThumbnailProps, FileCategory, GridV2Variant, } from './types';
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
2
|
import { type GridListItemProps, type GridListProps as RAGridListProps } from 'react-aria-components';
|
|
3
3
|
import { type IconButtonProps } from '../../primitives/icon-button';
|
|
4
|
+
import { type FileCategory } from '../../types/file-category';
|
|
4
5
|
import { type Modify } from '../../types/modify';
|
|
6
|
+
export type { FileCategory };
|
|
5
7
|
export interface BaseGridListLayoutProp {
|
|
6
|
-
layoutStyle: 'grid' | 'list' | 'small-list';
|
|
8
|
+
layoutStyle: 'grid' | 'grid-v2' | 'list' | 'small-list';
|
|
7
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Size variant for grid-v2 layout.
|
|
12
|
+
* - 'large': Min width 375px, good for detailed thumbnails (default)
|
|
13
|
+
* - 'small': Min width 250px, good for compact grids with more items
|
|
14
|
+
*/
|
|
15
|
+
export type GridV2Variant = 'large' | 'small';
|
|
8
16
|
interface CustomGridListProps extends BaseGridListLayoutProp {
|
|
9
17
|
/**
|
|
10
18
|
* This flag allows the consumer to opt-out of the default
|
|
@@ -14,17 +22,95 @@ interface CustomGridListProps extends BaseGridListLayoutProp {
|
|
|
14
22
|
* @param {boolean} isInteractive
|
|
15
23
|
*/
|
|
16
24
|
isInteractive?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Centers the text in the Header and Subtitle components.
|
|
27
|
+
* Only applies to grid-v2 layout. Has no effect on other layouts.
|
|
28
|
+
* Useful for folder-like items or centered layouts.
|
|
29
|
+
*/
|
|
30
|
+
centerText?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Size variant for grid-v2 layout. Controls minimum item width.
|
|
33
|
+
* - 'large': Min width 375px (~23.4rem), max 1fr - good for detailed thumbnails
|
|
34
|
+
* - 'small': Min width 250px (~15.6rem), max 1fr - good for compact grids
|
|
35
|
+
*
|
|
36
|
+
* Items will expand to fill available space (like grid-v1).
|
|
37
|
+
* Only applies to grid-v2 layout. Has no effect on other layouts.
|
|
38
|
+
* @default 'large'
|
|
39
|
+
*/
|
|
40
|
+
variant?: GridV2Variant;
|
|
17
41
|
}
|
|
18
42
|
export declare enum ListLayout {
|
|
19
43
|
GRID = "Grid List",
|
|
44
|
+
GRID_V2 = "Grid List V2",
|
|
20
45
|
LARGE_LIST = "Large List",
|
|
21
46
|
SMALL_LIST = "Small List"
|
|
22
47
|
}
|
|
23
48
|
export type BaseGridListHeaderProps = ComponentPropsWithRef<'span'> & {
|
|
24
49
|
textValue?: string;
|
|
25
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Props for the GridListV2 Thumbnail component.
|
|
53
|
+
*
|
|
54
|
+
* The thumbnail supports displaying a file type pill badge that shows the file extension
|
|
55
|
+
* with a color based on the file category. To use this feature:
|
|
56
|
+
*
|
|
57
|
+
* 1. Pass `fileExtension` - the file's extension (e.g., "pdf", "docx", "mp4")
|
|
58
|
+
* 2. Pass `fileCategory` - determines the pill's text color (use `getFileIconType` from `@box/item-icon`)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```tsx
|
|
62
|
+
* import { getFileIconType } from '@box/item-icon';
|
|
63
|
+
*
|
|
64
|
+
* // Get the category for color mapping
|
|
65
|
+
* const category = getFileIconType('docx'); // returns 'word-document'
|
|
66
|
+
*
|
|
67
|
+
* <GridListV2.Thumbnail fileExtension="docx" fileCategory={category}>
|
|
68
|
+
* <img src={thumbnailUrl} alt={fileName} />
|
|
69
|
+
* </GridListV2.Thumbnail>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
26
72
|
export type BaseGridListThumbnailProps = ComponentPropsWithRef<'div'> & {
|
|
73
|
+
/** When true, the thumbnail will not apply default sizing styles. */
|
|
27
74
|
hasCustomSize?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* File extension to display in the file type pill (e.g., "pdf", "docx", "mp4").
|
|
77
|
+
* Will be normalized to uppercase for display (e.g., "pdf" → "PDF").
|
|
78
|
+
*/
|
|
79
|
+
fileExtension?: string;
|
|
80
|
+
/**
|
|
81
|
+
* File category that determines the pill's text color.
|
|
82
|
+
* Use `getFileIconType(extension)` from `@box/item-icon` to get this value.
|
|
83
|
+
* If not provided, the pill will use the default black text color.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* import { getFileIconType } from '@box/item-icon';
|
|
88
|
+
*
|
|
89
|
+
* const fileCategory = getFileIconType('docx'); // returns 'word-document'
|
|
90
|
+
* // The pill will display "DOCX" with blue text (word-document color)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
fileCategory?: FileCategory;
|
|
94
|
+
/**
|
|
95
|
+
* Optional status badge element to render on the thumbnail (left of the file type pill).
|
|
96
|
+
* Typically used for classification badges with tooltips.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* <GridListV2.Thumbnail
|
|
101
|
+
* fileExtension="xlsx"
|
|
102
|
+
* fileCategory="excel-spreadsheet"
|
|
103
|
+
* statusBadge={
|
|
104
|
+
* <CardTooltipV2 content="Classification: Confidential">
|
|
105
|
+
* <Status colorIndex={2} hideText icon={Shield} text="Confidential" />
|
|
106
|
+
* </CardTooltipV2>
|
|
107
|
+
* }
|
|
108
|
+
* >
|
|
109
|
+
* <img src={thumbnailUrl} alt={fileName} />
|
|
110
|
+
* </GridListV2.Thumbnail>
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
statusBadge?: ReactNode;
|
|
28
114
|
};
|
|
29
115
|
export type BaseGridListSubtitleProps = ComponentPropsWithRef<'span'>;
|
|
30
116
|
export type BaseGridListDescriptionProps = ComponentPropsWithRef<'p'>;
|
|
@@ -41,8 +127,10 @@ export interface BaseGridListItemContextType {
|
|
|
41
127
|
pinAriaLabel?: string;
|
|
42
128
|
isItemInteracted: boolean;
|
|
43
129
|
setIsItemInteracted: (isInteracted: boolean) => void;
|
|
130
|
+
/** Whether the item has action buttons. Used to keep subtitle visible when no actions exist. */
|
|
131
|
+
hasActions: boolean;
|
|
132
|
+
setHasActions: (hasActions: boolean) => void;
|
|
44
133
|
}
|
|
45
|
-
export interface BaseGridListItemProps extends GridListItemProps, Omit<BaseGridListItemContextType, 'isItemInteracted' | 'setIsItemInteracted'> {
|
|
134
|
+
export interface BaseGridListItemProps extends GridListItemProps, Omit<BaseGridListItemContextType, 'isItemInteracted' | 'setIsItemInteracted' | 'hasActions' | 'setHasActions'> {
|
|
46
135
|
children?: ReactNode;
|
|
47
136
|
}
|
|
48
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.115.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@ariakit/react": "0.4.15",
|
|
49
49
|
"@ariakit/react-core": "0.4.15",
|
|
50
|
-
"@box/blueprint-web-assets": "^4.91.
|
|
50
|
+
"@box/blueprint-web-assets": "^4.91.6",
|
|
51
51
|
"@internationalized/date": "^3.7.0",
|
|
52
52
|
"@radix-ui/react-accordion": "1.1.2",
|
|
53
53
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"type-fest": "^3.2.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@box/storybook-utils": "^0.15.
|
|
80
|
+
"@box/storybook-utils": "^0.15.6",
|
|
81
81
|
"@types/react": "^18.0.0",
|
|
82
82
|
"@types/react-dom": "^18.0.0",
|
|
83
83
|
"react": "^18.3.0",
|