@oxy.so/bloom 5.3.0 → 5.4.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/docs/adoption-matrix.mdx +6 -6
- package/docs/ai-chat.mdx +4 -0
- package/docs/app-shell.mdx +17 -0
- package/docs/mail-list.mdx +22 -22
- package/docs/settings-modal.mdx +1 -1
- package/lib/commonjs/ai-chat/AiChatShell.js +230 -206
- package/lib/commonjs/ai-chat/AiChatShell.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellSplit.js +48 -22
- package/lib/commonjs/app-shell/AppShellSplit.js.map +1 -1
- package/lib/commonjs/app-shell/index.js +7 -0
- package/lib/commonjs/app-shell/index.js.map +1 -1
- package/lib/commonjs/app-shell/index.web.js +7 -0
- package/lib/commonjs/app-shell/index.web.js.map +1 -1
- package/lib/commonjs/icons/remix/RiFormatClear.js +12 -0
- package/lib/commonjs/icons/remix/RiFormatClear.js.map +1 -0
- package/lib/commonjs/icons/remix/RiListOrdered.js +12 -0
- package/lib/commonjs/icons/remix/RiListOrdered.js.map +1 -0
- package/lib/commonjs/icons/remix/RiStrikethrough.js +12 -0
- package/lib/commonjs/icons/remix/RiStrikethrough.js.map +1 -0
- package/lib/commonjs/icons/remix/RiUnderline.js +12 -0
- package/lib/commonjs/icons/remix/RiUnderline.js.map +1 -0
- package/lib/commonjs/icons/remix/index.js +28 -0
- package/lib/commonjs/icons/remix/index.js.map +1 -1
- package/lib/commonjs/mail-list/MailList.js +2 -0
- package/lib/commonjs/mail-list/MailList.js.map +1 -1
- package/lib/commonjs/mail-list/MailRow.js +26 -29
- package/lib/commonjs/mail-list/MailRow.js.map +1 -1
- package/lib/commonjs/mail-list/shared.js +26 -10
- package/lib/commonjs/mail-list/shared.js.map +1 -1
- package/lib/commonjs/settings-modal/SettingsModal.js +12 -18
- package/lib/commonjs/settings-modal/SettingsModal.js.map +1 -1
- package/lib/module/ai-chat/AiChatShell.js +231 -207
- package/lib/module/ai-chat/AiChatShell.js.map +1 -1
- package/lib/module/app-shell/AppShellSplit.js +49 -21
- package/lib/module/app-shell/AppShellSplit.js.map +1 -1
- package/lib/module/app-shell/index.js +1 -0
- package/lib/module/app-shell/index.js.map +1 -1
- package/lib/module/app-shell/index.web.js +1 -0
- package/lib/module/app-shell/index.web.js.map +1 -1
- package/lib/module/icons/remix/RiFormatClear.js +9 -0
- package/lib/module/icons/remix/RiFormatClear.js.map +1 -0
- package/lib/module/icons/remix/RiListOrdered.js +9 -0
- package/lib/module/icons/remix/RiListOrdered.js.map +1 -0
- package/lib/module/icons/remix/RiStrikethrough.js +9 -0
- package/lib/module/icons/remix/RiStrikethrough.js.map +1 -0
- package/lib/module/icons/remix/RiUnderline.js +9 -0
- package/lib/module/icons/remix/RiUnderline.js.map +1 -0
- package/lib/module/icons/remix/index.js +4 -0
- package/lib/module/icons/remix/index.js.map +1 -1
- package/lib/module/mail-list/MailList.js +2 -0
- package/lib/module/mail-list/MailList.js.map +1 -1
- package/lib/module/mail-list/MailRow.js +26 -29
- package/lib/module/mail-list/MailRow.js.map +1 -1
- package/lib/module/mail-list/shared.js +26 -10
- package/lib/module/mail-list/shared.js.map +1 -1
- package/lib/module/settings-modal/SettingsModal.js +12 -18
- package/lib/module/settings-modal/SettingsModal.js.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatShell.d.ts +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatShell.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/types.d.ts +7 -0
- package/lib/typescript/commonjs/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/AppShellSplit.d.ts +1 -20
- package/lib/typescript/commonjs/app-shell/AppShellSplit.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/index.d.ts +2 -0
- package/lib/typescript/commonjs/app-shell/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/index.web.d.ts +2 -0
- package/lib/typescript/commonjs/app-shell/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/types.d.ts +28 -0
- package/lib/typescript/commonjs/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/icons/remix/RiFormatClear.d.ts +8 -0
- package/lib/typescript/commonjs/icons/remix/RiFormatClear.d.ts.map +1 -0
- package/lib/typescript/commonjs/icons/remix/RiListOrdered.d.ts +8 -0
- package/lib/typescript/commonjs/icons/remix/RiListOrdered.d.ts.map +1 -0
- package/lib/typescript/commonjs/icons/remix/RiStrikethrough.d.ts +8 -0
- package/lib/typescript/commonjs/icons/remix/RiStrikethrough.d.ts.map +1 -0
- package/lib/typescript/commonjs/icons/remix/RiUnderline.d.ts +8 -0
- package/lib/typescript/commonjs/icons/remix/RiUnderline.d.ts.map +1 -0
- package/lib/typescript/commonjs/icons/remix/index.d.ts +4 -0
- package/lib/typescript/commonjs/icons/remix/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-list/MailList.d.ts +1 -1
- package/lib/typescript/commonjs/mail-list/MailList.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-list/MailRow.d.ts +5 -20
- package/lib/typescript/commonjs/mail-list/MailRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-list/shared.d.ts +2 -9
- package/lib/typescript/commonjs/mail-list/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-list/types.d.ts +8 -6
- package/lib/typescript/commonjs/mail-list/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatShell.d.ts +1 -1
- package/lib/typescript/module/ai-chat/AiChatShell.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/types.d.ts +7 -0
- package/lib/typescript/module/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/AppShellSplit.d.ts +1 -20
- package/lib/typescript/module/app-shell/AppShellSplit.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/index.d.ts +2 -0
- package/lib/typescript/module/app-shell/index.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/index.web.d.ts +2 -0
- package/lib/typescript/module/app-shell/index.web.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/types.d.ts +28 -0
- package/lib/typescript/module/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/module/icons/remix/RiFormatClear.d.ts +8 -0
- package/lib/typescript/module/icons/remix/RiFormatClear.d.ts.map +1 -0
- package/lib/typescript/module/icons/remix/RiListOrdered.d.ts +8 -0
- package/lib/typescript/module/icons/remix/RiListOrdered.d.ts.map +1 -0
- package/lib/typescript/module/icons/remix/RiStrikethrough.d.ts +8 -0
- package/lib/typescript/module/icons/remix/RiStrikethrough.d.ts.map +1 -0
- package/lib/typescript/module/icons/remix/RiUnderline.d.ts +8 -0
- package/lib/typescript/module/icons/remix/RiUnderline.d.ts.map +1 -0
- package/lib/typescript/module/icons/remix/index.d.ts +4 -0
- package/lib/typescript/module/icons/remix/index.d.ts.map +1 -1
- package/lib/typescript/module/mail-list/MailList.d.ts +1 -1
- package/lib/typescript/module/mail-list/MailList.d.ts.map +1 -1
- package/lib/typescript/module/mail-list/MailRow.d.ts +5 -20
- package/lib/typescript/module/mail-list/MailRow.d.ts.map +1 -1
- package/lib/typescript/module/mail-list/shared.d.ts +2 -9
- package/lib/typescript/module/mail-list/shared.d.ts.map +1 -1
- package/lib/typescript/module/mail-list/types.d.ts +8 -6
- package/lib/typescript/module/mail-list/types.d.ts.map +1 -1
- package/lib/typescript/module/settings-modal/SettingsModal.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/support/adoption-matrix.ts +708 -0
- package/src/__tests__/support/card-surface.ts +17 -0
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/commerce-harness.tsx +97 -0
- package/src/__tests__/support/composite.ts +68 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/messages-in.ts +10 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/__tests__/support/worklet-capture-fixture.tsx +28 -0
- package/src/ai-chat/AiChatShell.tsx +23 -4
- package/src/ai-chat/types.ts +7 -0
- package/src/app-shell/AppShellSplit.tsx +37 -41
- package/src/app-shell/index.ts +3 -0
- package/src/app-shell/index.web.ts +3 -0
- package/src/app-shell/types.ts +30 -0
- package/src/icons/remix/RiFormatClear.tsx +6 -0
- package/src/icons/remix/RiListOrdered.tsx +6 -0
- package/src/icons/remix/RiStrikethrough.tsx +6 -0
- package/src/icons/remix/RiUnderline.tsx +6 -0
- package/src/icons/remix/index.ts +4 -0
- package/src/mail-list/MailList.tsx +2 -0
- package/src/mail-list/MailRow.tsx +24 -33
- package/src/mail-list/shared.ts +27 -10
- package/src/mail-list/types.ts +8 -6
- package/src/settings-modal/SettingsModal.tsx +12 -15
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +9346 -0
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +69 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
|
@@ -19,28 +19,13 @@ import { ScrollView, View } from 'react-native';
|
|
|
19
19
|
|
|
20
20
|
import { AiChatResizeHandle } from '../ai-chat/AiChatResizeHandle';
|
|
21
21
|
import { Z_INDEX } from '../styles/z-index';
|
|
22
|
+
import { space } from '../styles/tokens';
|
|
22
23
|
import { useTheme } from '../theme/use-theme';
|
|
24
|
+
import { useMessages } from '../locale/messages';
|
|
25
|
+
import { APP_SHELL_MESSAGES } from './messages';
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
detail?: React.ReactNode;
|
|
27
|
-
info?: React.ReactNode;
|
|
28
|
-
/** Which panes have room right now. */
|
|
29
|
-
showList: boolean;
|
|
30
|
-
showDetail: boolean;
|
|
31
|
-
showInfo: boolean;
|
|
32
|
-
/** The list pane's resting width; a drag moves it within min/max. */
|
|
33
|
-
listWidth: number;
|
|
34
|
-
listMinWidth: number;
|
|
35
|
-
listMaxWidth: number;
|
|
36
|
-
onListWidthChange?: (width: number) => void;
|
|
37
|
-
infoWidth: number;
|
|
38
|
-
resizable: boolean;
|
|
39
|
-
/** The shell wraps each pane in its own `ScrollView` unless the page owns one. */
|
|
40
|
-
paneScroll: boolean;
|
|
41
|
-
resizeLabel: string;
|
|
42
|
-
testID?: string;
|
|
43
|
-
}
|
|
27
|
+
import type { AppShellSplitPanesProps } from './types';
|
|
28
|
+
import { APP_SHELL_DEFAULTS as D } from './constants';
|
|
44
29
|
|
|
45
30
|
/** One pane: a bounded column that scrolls its own overflow. */
|
|
46
31
|
function Pane({
|
|
@@ -68,23 +53,26 @@ function Pane({
|
|
|
68
53
|
}
|
|
69
54
|
|
|
70
55
|
const AppShellSplitPanesComponent: React.FC<AppShellSplitPanesProps> = ({
|
|
56
|
+
variant = 'joined',
|
|
71
57
|
list,
|
|
72
58
|
detail,
|
|
73
59
|
info,
|
|
74
|
-
showList,
|
|
75
|
-
showDetail,
|
|
76
|
-
showInfo,
|
|
77
|
-
listWidth,
|
|
78
|
-
listMinWidth,
|
|
79
|
-
listMaxWidth,
|
|
60
|
+
showList = list != null,
|
|
61
|
+
showDetail = true,
|
|
62
|
+
showInfo = info != null,
|
|
63
|
+
listWidth = D.listWidth,
|
|
64
|
+
listMinWidth = D.listMinWidth,
|
|
65
|
+
listMaxWidth = D.listMaxWidth,
|
|
80
66
|
onListWidthChange,
|
|
81
|
-
infoWidth,
|
|
82
|
-
resizable,
|
|
83
|
-
paneScroll,
|
|
84
|
-
resizeLabel,
|
|
67
|
+
infoWidth = D.infoWidth,
|
|
68
|
+
resizable = true,
|
|
69
|
+
paneScroll = true,
|
|
70
|
+
resizeLabel: resizeLabelProp,
|
|
85
71
|
testID,
|
|
86
72
|
}) => {
|
|
87
73
|
const theme = useTheme();
|
|
74
|
+
const { messages } = useMessages(APP_SHELL_MESSAGES);
|
|
75
|
+
const resizeLabel = resizeLabelProp ?? messages.resizePanes;
|
|
88
76
|
const [width, setWidth] = useState(listWidth);
|
|
89
77
|
const widthAtDragStart = useRef(listWidth);
|
|
90
78
|
|
|
@@ -116,11 +104,13 @@ const AppShellSplitPanesComponent: React.FC<AppShellSplitPanesProps> = ({
|
|
|
116
104
|
* hit test at the divider's centre returned the pane, and the drag never
|
|
117
105
|
* started. Raising the DIVIDER lifts the whole context over both panes.
|
|
118
106
|
*/
|
|
107
|
+
const separated = variant === 'separated';
|
|
119
108
|
const divider = {
|
|
120
|
-
width: 1,
|
|
109
|
+
width: separated ? space.md : 1,
|
|
110
|
+
flexShrink: 0,
|
|
121
111
|
alignSelf: 'stretch' as const,
|
|
122
112
|
zIndex: Z_INDEX.floating,
|
|
123
|
-
backgroundColor: theme.colors.border,
|
|
113
|
+
backgroundColor: separated ? 'transparent' : theme.colors.border,
|
|
124
114
|
};
|
|
125
115
|
// Only the panes that are actually drawn count: alone, a pane fills the row.
|
|
126
116
|
const solo = [showList, showDetail, showInfo].filter(Boolean).length === 1;
|
|
@@ -137,15 +127,21 @@ const AppShellSplitPanesComponent: React.FC<AppShellSplitPanesProps> = ({
|
|
|
137
127
|
</Pane>
|
|
138
128
|
) : null}
|
|
139
129
|
{showList && !solo ? (
|
|
140
|
-
<View style={divider}>
|
|
130
|
+
<View style={divider} testID={testID ? `${testID}-list-gap` : undefined}>
|
|
141
131
|
{resizable ? (
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
132
|
+
<View
|
|
133
|
+
pointerEvents="box-none"
|
|
134
|
+
testID={testID ? `${testID}-resize-anchor` : undefined}
|
|
135
|
+
style={separated ? { position: 'absolute', top: 0, bottom: 0, left: '50%', right: '50%', width: 0 } : { flex: 1 }}
|
|
136
|
+
>
|
|
137
|
+
<AiChatResizeHandle
|
|
138
|
+
label={resizeLabel}
|
|
139
|
+
onResizeStart={onResizeStart}
|
|
140
|
+
onResize={onResize}
|
|
141
|
+
onNudge={onNudge}
|
|
142
|
+
testID={testID ? `${testID}-divider` : undefined}
|
|
143
|
+
/>
|
|
144
|
+
</View>
|
|
149
145
|
) : null}
|
|
150
146
|
</View>
|
|
151
147
|
) : null}
|
|
@@ -158,7 +154,7 @@ const AppShellSplitPanesComponent: React.FC<AppShellSplitPanesProps> = ({
|
|
|
158
154
|
{detail}
|
|
159
155
|
</Pane>
|
|
160
156
|
) : null}
|
|
161
|
-
{showInfo && !solo ? <View style={divider} /> : null}
|
|
157
|
+
{showInfo && showDetail && !solo ? <View style={divider} testID={testID ? `${testID}-info-gap` : undefined} /> : null}
|
|
162
158
|
{showInfo ? (
|
|
163
159
|
<Pane
|
|
164
160
|
testID={testID ? `${testID}-pane-info` : undefined}
|
package/src/app-shell/index.ts
CHANGED
package/src/app-shell/types.ts
CHANGED
|
@@ -375,3 +375,33 @@ export interface AppShellNavigationProps {
|
|
|
375
375
|
export type AppShellProps = Omit<AppShellEngineProps, 'scroll'> & AppShellNavigationProps & {
|
|
376
376
|
scroll?: AppShellScroll | 'auto' | 'external';
|
|
377
377
|
};
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
/** A bounded list/detail/info layout, independent of navigation or surfaces. */
|
|
381
|
+
export interface AppShellSplitPanesProps {
|
|
382
|
+
/** Joined uses hairline dividers (default); separated leaves a 12px gutter for independent panel surfaces. */
|
|
383
|
+
variant?: 'joined' | 'separated';
|
|
384
|
+
list?: ReactNode;
|
|
385
|
+
detail?: ReactNode;
|
|
386
|
+
info?: ReactNode;
|
|
387
|
+
/** Defaults to whether list content was supplied. Hidden panes unmount. */
|
|
388
|
+
showList?: boolean;
|
|
389
|
+
/** Defaults to true. Keep true across breakpoints to retain routed detail state. */
|
|
390
|
+
showDetail?: boolean;
|
|
391
|
+
/** Defaults to whether info content was supplied. */
|
|
392
|
+
showInfo?: boolean;
|
|
393
|
+
/** Initial list width (360), constrained by listMinWidth/listMaxWidth (280/520). */
|
|
394
|
+
listWidth?: number;
|
|
395
|
+
listMinWidth?: number;
|
|
396
|
+
listMaxWidth?: number;
|
|
397
|
+
onListWidthChange?: (width: number) => void;
|
|
398
|
+
/** Info column width, defaults to 320. */
|
|
399
|
+
infoWidth?: number;
|
|
400
|
+
/** Show the shared draggable/keyboard divider, defaults to true. */
|
|
401
|
+
resizable?: boolean;
|
|
402
|
+
/** Wrap each pane in a ScrollView (default true); false for a navigator or virtual list. */
|
|
403
|
+
paneScroll?: boolean;
|
|
404
|
+
/** Accessible divider name, defaults to "Resize panes". */
|
|
405
|
+
resizeLabel?: string;
|
|
406
|
+
testID?: string;
|
|
407
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createSinglePathSVG } from '../TEMPLATE';
|
|
2
|
+
|
|
3
|
+
/** Remix Icon `format-clear` (Editor). */
|
|
4
|
+
export const RiFormatClear = createSinglePathSVG({
|
|
5
|
+
path: "M12.6512 14.0654L11.6047 20H9.57389L10.9247 12.339L3.51465 4.92892L4.92886 3.51471L20.4852 19.0711L19.071 20.4853L12.6512 14.0654ZM11.7727 7.53009L12.0425 5.99999H10.2426L8.24257 3.99999H19.9999V5.99999H14.0733L13.4991 9.25652L11.7727 7.53009Z",
|
|
6
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createSinglePathSVG } from '../TEMPLATE';
|
|
2
|
+
|
|
3
|
+
/** Remix Icon `list-ordered` (Editor). */
|
|
4
|
+
export const RiListOrdered = createSinglePathSVG({
|
|
5
|
+
path: "M8 4H21V6H8V4ZM5 3V6H6V7H3V6H4V4H3V3H5ZM3 14V11.5H5V11H3V10H6V12.5H4V13H6V14H3ZM5 19.5H3V18.5H5V18H3V17H6V21H3V20H5V19.5ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z",
|
|
6
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createSinglePathSVG } from '../TEMPLATE';
|
|
2
|
+
|
|
3
|
+
/** Remix Icon `strikethrough` (Editor). */
|
|
4
|
+
export const RiStrikethrough = createSinglePathSVG({
|
|
5
|
+
path: "M17.1538 14C17.3846 14.5161 17.5 15.0893 17.5 15.7196C17.5 17.0625 16.9762 18.1116 15.9286 18.867C14.8809 19.6223 13.4335 20 11.5862 20C9.94674 20 8.32335 19.6185 6.71592 18.8555V16.6009C8.23538 17.4783 9.7908 17.917 11.3822 17.917C13.9333 17.917 15.2128 17.1846 15.2208 15.7196C15.2208 15.0939 15.0049 14.5598 14.5731 14.1173C14.5339 14.0772 14.4939 14.0381 14.4531 14H3V12H21V14H17.1538ZM13.076 11H7.62908C7.4566 10.8433 7.29616 10.6692 7.14776 10.4778C6.71592 9.92084 6.5 9.24559 6.5 8.45207C6.5 7.21602 6.96583 6.165 7.89749 5.299C8.82916 4.43299 10.2706 4 12.2219 4C13.6934 4 15.1009 4.32808 16.4444 4.98426V7.13591C15.2448 6.44921 13.9293 6.10587 12.4978 6.10587C10.0187 6.10587 8.77917 6.88793 8.77917 8.45207C8.77917 8.87172 8.99709 9.23796 9.43293 9.55079C9.86878 9.86362 10.4066 10.1135 11.0463 10.3004C11.6665 10.4816 12.3431 10.7148 13.076 11H13.076Z",
|
|
6
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createSinglePathSVG } from '../TEMPLATE';
|
|
2
|
+
|
|
3
|
+
/** Remix Icon `underline` (Editor). */
|
|
4
|
+
export const RiUnderline = createSinglePathSVG({
|
|
5
|
+
path: "M8 3V12C8 14.2091 9.79086 16 12 16C14.2091 16 16 14.2091 16 12V3H18V12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12V3H8ZM4 20H20V22H4V20Z",
|
|
6
|
+
});
|
package/src/icons/remix/index.ts
CHANGED
|
@@ -197,6 +197,7 @@ export { RiFolderOpenLine } from './RiFolderOpenLine';
|
|
|
197
197
|
export { RiFontSize } from './RiFontSize';
|
|
198
198
|
export { RiFontSize2 } from './RiFontSize2';
|
|
199
199
|
export { RiForbidLine } from './RiForbidLine';
|
|
200
|
+
export { RiFormatClear } from './RiFormatClear';
|
|
200
201
|
export { RiForward10Line } from './RiForward10Line';
|
|
201
202
|
export { RiForward15Line } from './RiForward15Line';
|
|
202
203
|
export { RiForward30Line } from './RiForward30Line';
|
|
@@ -259,6 +260,7 @@ export { RiLink } from './RiLink';
|
|
|
259
260
|
export { RiLinkM } from './RiLinkM';
|
|
260
261
|
export { RiLinkedinBoxFill } from './RiLinkedinBoxFill';
|
|
261
262
|
export { RiListCheck3 } from './RiListCheck3';
|
|
263
|
+
export { RiListOrdered } from './RiListOrdered';
|
|
262
264
|
export { RiListUnordered } from './RiListUnordered';
|
|
263
265
|
export { RiListView } from './RiListView';
|
|
264
266
|
export { RiLiveLine } from './RiLiveLine';
|
|
@@ -417,6 +419,7 @@ export { RiStethoscopeLine } from './RiStethoscopeLine';
|
|
|
417
419
|
export { RiStockLine } from './RiStockLine';
|
|
418
420
|
export { RiStopFill } from './RiStopFill';
|
|
419
421
|
export { RiStore2Line } from './RiStore2Line';
|
|
422
|
+
export { RiStrikethrough } from './RiStrikethrough';
|
|
420
423
|
export { RiSubtractLine } from './RiSubtractLine';
|
|
421
424
|
export { RiSubwayLine } from './RiSubwayLine';
|
|
422
425
|
export { RiSuitcaseLine } from './RiSuitcaseLine';
|
|
@@ -446,6 +449,7 @@ export { RiTreeLine } from './RiTreeLine';
|
|
|
446
449
|
export { RiTv2Line } from './RiTv2Line';
|
|
447
450
|
export { RiTvLine } from './RiTvLine';
|
|
448
451
|
export { RiTwitterXFill } from './RiTwitterXFill';
|
|
452
|
+
export { RiUnderline } from './RiUnderline';
|
|
449
453
|
export { RiUnpinLine } from './RiUnpinLine';
|
|
450
454
|
export { RiUpload2Line } from './RiUpload2Line';
|
|
451
455
|
export { RiUploadCloud2Line } from './RiUploadCloud2Line';
|
|
@@ -54,6 +54,7 @@ function MailListComponent({
|
|
|
54
54
|
sections,
|
|
55
55
|
mails,
|
|
56
56
|
density = 'comfortable',
|
|
57
|
+
showAvatar = true,
|
|
57
58
|
selectedId,
|
|
58
59
|
checkedIds,
|
|
59
60
|
onCheckedIdsChange,
|
|
@@ -166,6 +167,7 @@ function MailListComponent({
|
|
|
166
167
|
actionsPlacement={mail.actionsPlacement ?? actionsPlacement}
|
|
167
168
|
swipeEnabled={mail.swipeEnabled ?? swipeEnabled}
|
|
168
169
|
density={density}
|
|
170
|
+
showAvatar={mail.showAvatar ?? showAvatar}
|
|
169
171
|
strings={mail.strings ?? strings}
|
|
170
172
|
selected={selectedId === id}
|
|
171
173
|
checked={checked.has(id)}
|
|
@@ -41,26 +41,11 @@ import type { MailAction, MailRowProps } from './types';
|
|
|
41
41
|
/**
|
|
42
42
|
* One message thread in the inbox.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* the right where it is a control
|
|
47
|
-
* compact 40 tall · 12 padding · 24 avatar · sender, then subject and
|
|
48
|
-
* snippet on ONE baseline, then the states and the time
|
|
44
|
+
* Compact uses one baseline; comfortable and cozy stack sender above subject
|
|
45
|
+
* and snippet. All three share a geometry table, content, paint and actions.
|
|
49
46
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* draws too, and a density that forked into two files is a density that drifts.
|
|
53
|
-
*
|
|
54
|
-
* TWO LINES, NOT THREE, AND NOTHING DRAWN AT REST THAT IS NOT INFORMATION.
|
|
55
|
-
* The phone row is the same 72/48 as a conversation row, so an inbox and a
|
|
56
|
-
* messages screen read as one library. What paid for the third line: the
|
|
57
|
-
* subject and the snippet share line two, the labels collapse to a chip and
|
|
58
|
-
* dots, and the star is drawn only where it is a control.
|
|
59
|
-
*
|
|
60
|
-
* THE SUBJECT AND THE SNIPPET ARE ONE `Text`, at both densities. Two
|
|
61
|
-
* neighbouring flex children each want to be as wide as their content, and the
|
|
62
|
-
* result of a long one is a row that pushes sideways or a subject clipped to
|
|
63
|
-
* nothing. One string with a nested span truncates where the line ends.
|
|
47
|
+
* The subject and snippet share ONE Text at every density, so a long snippet
|
|
48
|
+
* cannot claim its own flex width and push the subject out of the row.
|
|
64
49
|
*
|
|
65
50
|
* UNREAD IS THE WHOLE ROW, NOT A DOT. The sender and the subject go semibold
|
|
66
51
|
* and every rung of text moves up to the primary colour; a read row sits on the
|
|
@@ -97,6 +82,7 @@ function MailRowComponent({
|
|
|
97
82
|
checked = false,
|
|
98
83
|
onCheckedChange,
|
|
99
84
|
density = 'comfortable',
|
|
85
|
+
showAvatar = true,
|
|
100
86
|
onPress,
|
|
101
87
|
onLongPress,
|
|
102
88
|
href,
|
|
@@ -210,6 +196,7 @@ function MailRowComponent({
|
|
|
210
196
|
|
|
211
197
|
const paperclip = hasAttachment ? (
|
|
212
198
|
<View
|
|
199
|
+
pointerEvents="none"
|
|
213
200
|
aria-hidden
|
|
214
201
|
importantForAccessibility="no-hide-descendants"
|
|
215
202
|
style={{ flexShrink: 0 }}
|
|
@@ -222,6 +209,7 @@ function MailRowComponent({
|
|
|
222
209
|
const threadBadge =
|
|
223
210
|
(threadCount ?? 0) > 1 ? (
|
|
224
211
|
<View
|
|
212
|
+
pointerEvents="none"
|
|
225
213
|
aria-hidden
|
|
226
214
|
importantForAccessibility="no-hide-descendants"
|
|
227
215
|
style={{ flexShrink: 0 }}
|
|
@@ -233,20 +221,22 @@ function MailRowComponent({
|
|
|
233
221
|
|
|
234
222
|
const timeText =
|
|
235
223
|
time === undefined ? null : (
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
224
|
+
<View pointerEvents="none" style={{ flexShrink: 0 }}>
|
|
225
|
+
<Text
|
|
226
|
+
variant={geo.timeVariant}
|
|
227
|
+
numberOfLines={1}
|
|
228
|
+
style={{ color: timeColor, flexShrink: 0 }}
|
|
229
|
+
testID={testID ? `${testID}-time` : undefined}
|
|
230
|
+
>
|
|
231
|
+
{time}
|
|
232
|
+
</Text>
|
|
233
|
+
</View>
|
|
244
234
|
);
|
|
245
235
|
|
|
246
236
|
// --- the leading slot: a checkbox when the list can multi-select ----------
|
|
247
237
|
const leading =
|
|
248
238
|
onCheckedChange !== undefined ? (
|
|
249
|
-
<View style={{ width: geo.avatar, alignItems: 'center', flexShrink: 0 }}>
|
|
239
|
+
<View pointerEvents="box-none" style={{ width: geo.avatar, alignItems: 'center', flexShrink: 0 }}>
|
|
250
240
|
<Checkbox
|
|
251
241
|
checked={checked}
|
|
252
242
|
onCheckedChange={onCheckedChange}
|
|
@@ -255,7 +245,7 @@ function MailRowComponent({
|
|
|
255
245
|
testID={testID ? `${testID}-checkbox` : undefined}
|
|
256
246
|
/>
|
|
257
247
|
</View>
|
|
258
|
-
) : (
|
|
248
|
+
) : showAvatar ? (
|
|
259
249
|
<View
|
|
260
250
|
pointerEvents="none"
|
|
261
251
|
aria-hidden
|
|
@@ -269,7 +259,7 @@ function MailRowComponent({
|
|
|
269
259
|
testID={testID ? `${testID}-avatar` : undefined}
|
|
270
260
|
/>
|
|
271
261
|
</View>
|
|
272
|
-
);
|
|
262
|
+
) : null;
|
|
273
263
|
|
|
274
264
|
// --- the text column -----------------------------------------------------
|
|
275
265
|
const column = compact ? (
|
|
@@ -283,7 +273,7 @@ function MailRowComponent({
|
|
|
283
273
|
flexDirection: 'row',
|
|
284
274
|
alignItems: 'center',
|
|
285
275
|
gap: geo.gap,
|
|
286
|
-
marginLeft: geo.gap,
|
|
276
|
+
marginLeft: leading === null ? 0 : geo.gap,
|
|
287
277
|
}}
|
|
288
278
|
>
|
|
289
279
|
<Text
|
|
@@ -318,7 +308,7 @@ function MailRowComponent({
|
|
|
318
308
|
pointerEvents="none"
|
|
319
309
|
aria-hidden
|
|
320
310
|
importantForAccessibility="no-hide-descendants"
|
|
321
|
-
style={{ flex: 1, minWidth: 0, marginLeft: geo.gap, gap: geo.lineGap }}
|
|
311
|
+
style={{ flex: 1, minWidth: 0, marginLeft: leading === null ? 0 : geo.gap, gap: geo.lineGap }}
|
|
322
312
|
>
|
|
323
313
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, minWidth: 0 }}>
|
|
324
314
|
<Text
|
|
@@ -374,7 +364,7 @@ function MailRowComponent({
|
|
|
374
364
|
// and every star press would open the thread. Everything else in here is
|
|
375
365
|
// hidden from assistive technology; the row's composed name already says it.
|
|
376
366
|
const states = paperclip || threadBadge || onStarredChange !== undefined ? (
|
|
377
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
367
|
+
<View pointerEvents="box-none" style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
|
|
378
368
|
{paperclip}
|
|
379
369
|
{threadBadge}
|
|
380
370
|
{onStarredChange === undefined ? (starred ? star : null) : star}
|
|
@@ -447,6 +437,7 @@ function MailRowComponent({
|
|
|
447
437
|
testID={testID ? `${testID}-link` : undefined}
|
|
448
438
|
/>
|
|
449
439
|
<View
|
|
440
|
+
pointerEvents="box-none"
|
|
450
441
|
style={{
|
|
451
442
|
flexDirection: 'row',
|
|
452
443
|
alignItems: 'center',
|
package/src/mail-list/shared.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
|
|
3
|
+
import { space } from '../styles/tokens';
|
|
4
|
+
|
|
3
5
|
import { RiInbox2Line } from '../icons/remix/RiInbox2Line';
|
|
4
6
|
import { formatGregorian } from '../locale/format-date';
|
|
5
7
|
import { pickMessages } from '../locale/messages';
|
|
@@ -31,7 +33,7 @@ export const IS_WEB = Platform.OS === 'web';
|
|
|
31
33
|
export const MAIL_EMPTY_ICON = RiInbox2Line;
|
|
32
34
|
|
|
33
35
|
// ---------------------------------------------------------------------------
|
|
34
|
-
// Geometry — the whole difference between the
|
|
36
|
+
// Geometry — the whole difference between the densities
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
36
38
|
|
|
37
39
|
export interface MailRowGeometry {
|
|
@@ -85,15 +87,8 @@ export interface MailRowGeometry {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* snippet and a label.
|
|
91
|
-
*
|
|
92
|
-
* The phone rung is the same 72/48 as a `chat-list` row on purpose: a mail row
|
|
93
|
-
* and a conversation row are the same kind of object, and an inbox that stands
|
|
94
|
-
* 16px taller per row than the messages screen reads as a different library.
|
|
95
|
-
* Two lines is what pays for it — the sender and the time share the first, the
|
|
96
|
-
* subject and the snippet the second.
|
|
90
|
+
* Two stacked lines at comfortable/cozy, one baseline at compact. Stacked rows
|
|
91
|
+
* grow with their content; cozy gives the same facts more breathing room.
|
|
97
92
|
*/
|
|
98
93
|
export const MAIL_ROW_GEOMETRY: Record<MailDensity, MailRowGeometry> = {
|
|
99
94
|
comfortable: {
|
|
@@ -121,6 +116,27 @@ export const MAIL_ROW_GEOMETRY: Record<MailDensity, MailRowGeometry> = {
|
|
|
121
116
|
actionGlyph: 18,
|
|
122
117
|
starControl: 24,
|
|
123
118
|
},
|
|
119
|
+
cozy: {
|
|
120
|
+
minHeight: 80,
|
|
121
|
+
paddingVertical: space.lg,
|
|
122
|
+
paddingHorizontal: space.lg,
|
|
123
|
+
avatar: 48,
|
|
124
|
+
gap: space.md,
|
|
125
|
+
lineGap: space._2xs,
|
|
126
|
+
glyph: space.lg,
|
|
127
|
+
senderWidth: 0,
|
|
128
|
+
senderMinWidth: 0,
|
|
129
|
+
senderMaxWidth: '100%',
|
|
130
|
+
senderVariant: 'headline-regular',
|
|
131
|
+
senderUnreadVariant: 'headline-semibold',
|
|
132
|
+
subjectVariant: 'body-regular',
|
|
133
|
+
subjectUnreadVariant: 'body-semibold',
|
|
134
|
+
snippetVariant: 'body-regular',
|
|
135
|
+
timeVariant: 'caption-1-regular',
|
|
136
|
+
action: 44,
|
|
137
|
+
actionGlyph: 18,
|
|
138
|
+
starControl: space._2xl,
|
|
139
|
+
},
|
|
124
140
|
compact: {
|
|
125
141
|
minHeight: 40,
|
|
126
142
|
height: 40,
|
|
@@ -164,6 +180,7 @@ export const MAIL_LABEL_DOT = 8;
|
|
|
164
180
|
/** The selection bar's height, per density. */
|
|
165
181
|
export const MAIL_SELECTION_BAR_HEIGHT: Record<MailDensity, number> = {
|
|
166
182
|
comfortable: 56,
|
|
183
|
+
cozy: 56,
|
|
167
184
|
compact: 44,
|
|
168
185
|
};
|
|
169
186
|
|
package/src/mail-list/types.ts
CHANGED
|
@@ -5,13 +5,11 @@ import type { BloomIconComponent } from '../icons/icon-component';
|
|
|
5
5
|
import type { AccentTone } from '../theme/accent-colors';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the desktop list: one line, 40 tall, the same facts on a single baseline.
|
|
12
|
-
* Nothing but the geometry table changes between them.
|
|
8
|
+
* One row at three densities: compact is a single 40px baseline, comfortable
|
|
9
|
+
* stacks two lines at a 64px floor, cozy keeps those lines with an 80px floor
|
|
10
|
+
* and a larger avatar and insets. All share the same content and actions.
|
|
13
11
|
*/
|
|
14
|
-
export type MailDensity = 'comfortable' | 'compact';
|
|
12
|
+
export type MailDensity = 'comfortable' | 'cozy' | 'compact';
|
|
15
13
|
|
|
16
14
|
/** Who a message is from, as a row draws them. */
|
|
17
15
|
export interface MailSender {
|
|
@@ -154,6 +152,8 @@ export interface MailRowProps {
|
|
|
154
152
|
*/
|
|
155
153
|
checked?: boolean;
|
|
156
154
|
onCheckedChange?: (checked: boolean) => void;
|
|
155
|
+
/** Show sender avatars. Selection checkboxes remain visible when enabled. */
|
|
156
|
+
showAvatar?: boolean;
|
|
157
157
|
density?: MailDensity;
|
|
158
158
|
onPress?: () => void;
|
|
159
159
|
onLongPress?: () => void;
|
|
@@ -203,6 +203,8 @@ export interface MailListProps {
|
|
|
203
203
|
sections?: readonly MailListSection[];
|
|
204
204
|
/** One unlabelled run, for a list that is not bucketed. */
|
|
205
205
|
mails?: readonly MailSummary[];
|
|
206
|
+
/** Show sender avatars. Selection checkboxes remain visible when enabled. */
|
|
207
|
+
showAvatar?: boolean;
|
|
206
208
|
density?: MailDensity;
|
|
207
209
|
/** The row the reading pane is showing. */
|
|
208
210
|
selectedId?: string;
|
|
@@ -25,6 +25,7 @@ import { PageHeader } from '../page-header';
|
|
|
25
25
|
import { ButtonGroup, ButtonGroupItem } from '../button-group';
|
|
26
26
|
import { RiCloseLine } from '../icons/remix/RiCloseLine';
|
|
27
27
|
import { Backdrop, OverlayRoot } from '../overlay';
|
|
28
|
+
import { ModalKeyboard } from '../overlay/ModalKeyboard';
|
|
28
29
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
29
30
|
import { webDataSet } from '../styles/web-data';
|
|
30
31
|
import { Text } from '../typography';
|
|
@@ -139,6 +140,7 @@ export function SettingsModal({
|
|
|
139
140
|
labels,
|
|
140
141
|
testID,
|
|
141
142
|
}: SettingsModalProps) {
|
|
143
|
+
const panelRef = useRef<View>(null);
|
|
142
144
|
const common = useCommonMessages();
|
|
143
145
|
const { messages } = useMessages(SETTINGS_MODAL_MESSAGES);
|
|
144
146
|
useSettingsWebCss();
|
|
@@ -253,21 +255,6 @@ export function SettingsModal({
|
|
|
253
255
|
if (mayLeave('navigation')) setCompactPageOpen(false);
|
|
254
256
|
}, [mayLeave]);
|
|
255
257
|
|
|
256
|
-
// Escape closes (web). On `window`, not `document`: a menu or popover opened
|
|
257
|
-
// inside the modal handles Escape on `document` and stops propagation there,
|
|
258
|
-
// so the innermost surface closes first and the modal stays open.
|
|
259
|
-
useEffect(() => {
|
|
260
|
-
if (!mounted || !IS_WEB || typeof window === 'undefined') return;
|
|
261
|
-
const handler = (event: KeyboardEvent) => {
|
|
262
|
-
if (event.key === 'Escape') {
|
|
263
|
-
event.stopPropagation();
|
|
264
|
-
requestClose();
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
window.addEventListener('keydown', handler);
|
|
268
|
-
return () => window.removeEventListener('keydown', handler);
|
|
269
|
-
}, [mounted, requestClose]);
|
|
270
|
-
|
|
271
258
|
const compactPageOpenRef = useRef(false);
|
|
272
259
|
compactPageOpenRef.current = compactPageOpen && layoutRef.current === 'compact';
|
|
273
260
|
|
|
@@ -355,6 +342,14 @@ export function SettingsModal({
|
|
|
355
342
|
<ModalPortal>
|
|
356
343
|
<SettingsModalContext.Provider value={context}>
|
|
357
344
|
<OverlayRoot onRequestClose={onHardwareBack} modal>
|
|
345
|
+
{IS_WEB ? (
|
|
346
|
+
<ModalKeyboard
|
|
347
|
+
panelRef={panelRef}
|
|
348
|
+
closing={!visible && wasVisible.current}
|
|
349
|
+
dismissible
|
|
350
|
+
dismiss={requestClose}
|
|
351
|
+
/>
|
|
352
|
+
) : null}
|
|
358
353
|
<Backdrop
|
|
359
354
|
onPress={requestClose}
|
|
360
355
|
progress={progress}
|
|
@@ -377,6 +372,8 @@ export function SettingsModal({
|
|
|
377
372
|
]}
|
|
378
373
|
>
|
|
379
374
|
<View
|
|
375
|
+
ref={panelRef}
|
|
376
|
+
tabIndex={IS_WEB ? -1 : undefined}
|
|
380
377
|
role="dialog"
|
|
381
378
|
aria-modal
|
|
382
379
|
aria-label={labels?.dialog ?? messages.dialog}
|