@astryxdesign/lab 0.7.7-canary.c0929c8 → 0.7.7-canary.c19ca12
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/InfoTip/index.d.ts +21 -3
- package/dist/InfoTip/index.d.ts.map +1 -1
- package/dist/InfoTip/index.js +22 -2
- package/dist/MobilePowerSearch/MobilePowerSearch.d.ts.map +1 -1
- package/dist/MobilePowerSearch/MobilePowerSearch.js +5 -4
- package/dist/MobileTokenizer/MobileTokenizer.d.ts +1 -1
- package/dist/MobileTokenizer/MobileTokenizer.d.ts.map +1 -1
- package/dist/MobileTokenizer/MobileTokenizer.js +10 -37
- package/dist/lab.css +0 -3
- package/package.json +5 -5
- package/src/InfoTip/InfoTip.test.tsx +24 -229
- package/src/InfoTip/index.ts +28 -3
- package/src/MobilePowerSearch/MobilePowerSearch.test.tsx +11 -0
- package/src/MobilePowerSearch/MobilePowerSearch.tsx +18 -7
- package/src/MobileTokenizer/MobileTokenizer.test.tsx +6 -5
- package/src/MobileTokenizer/MobileTokenizer.tsx +25 -51
- package/dist/InfoTip/InfoTip.d.ts +0 -58
- package/dist/InfoTip/InfoTip.d.ts.map +0 -1
- package/dist/InfoTip/InfoTip.js +0 -94
- package/dist/internal/MobileSheetConfirmation.d.ts +0 -12
- package/dist/internal/MobileSheetConfirmation.d.ts.map +0 -1
- package/dist/internal/MobileSheetConfirmation.js +0 -88
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts +0 -21
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +0 -1
- package/dist/internal/useDismissSearchOnTouchDrag.js +0 -43
- package/src/InfoTip/InfoTip.doc.mjs +0 -100
- package/src/InfoTip/InfoTip.tsx +0 -128
- package/src/internal/MobileSheetConfirmation.test.tsx +0 -68
- package/src/internal/MobileSheetConfirmation.tsx +0 -97
- package/src/internal/useDismissSearchOnTouchDrag.test.tsx +0 -94
- package/src/internal/useDismissSearchOnTouchDrag.ts +0 -57
|
@@ -142,8 +142,11 @@ import {
|
|
|
142
142
|
typographyVars,
|
|
143
143
|
} from '@astryxdesign/core/theme/tokens.stylex';
|
|
144
144
|
import {isImeKeyEvent, isRenderable, rtlStyles} from '@astryxdesign/core/utils';
|
|
145
|
-
|
|
146
|
-
import {
|
|
145
|
+
|
|
146
|
+
import {
|
|
147
|
+
SheetConfirmation,
|
|
148
|
+
useDismissSearchOnTouchDrag,
|
|
149
|
+
} from '@astryxdesign/core/internal/touch-sheet';
|
|
147
150
|
import {MobilePowerSearchValueEditor} from './MobilePowerSearchValueEditor';
|
|
148
151
|
import {
|
|
149
152
|
createContentSearchSource,
|
|
@@ -889,10 +892,18 @@ export function MobilePowerSearch({
|
|
|
889
892
|
|
|
890
893
|
// ------------------------------------------------------------------ focus --
|
|
891
894
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
895
|
+
// A drag moves focus to the sheet's panel, never to the page body behind
|
|
896
|
+
// the open modal.
|
|
897
|
+
const fieldPickerDragProps = useDismissSearchOnTouchDrag(
|
|
898
|
+
() => [
|
|
899
|
+
contentSearchRef.current?.querySelector('input'),
|
|
900
|
+
fieldSearchRef.current?.querySelector('input'),
|
|
901
|
+
],
|
|
902
|
+
() =>
|
|
903
|
+
(
|
|
904
|
+
fieldSearchRef.current ?? contentSearchRef.current
|
|
905
|
+
)?.closest<HTMLElement>('[tabindex="-1"]'),
|
|
906
|
+
);
|
|
896
907
|
|
|
897
908
|
const focusPendingSheetTarget = useCallback(() => {
|
|
898
909
|
const target = pendingSheetFocusRef.current;
|
|
@@ -2356,7 +2367,7 @@ export function MobilePowerSearch({
|
|
|
2356
2367
|
</div>
|
|
2357
2368
|
</BottomSheet>
|
|
2358
2369
|
|
|
2359
|
-
<
|
|
2370
|
+
<SheetConfirmation
|
|
2360
2371
|
isOpen={isClearConfirmationOpen}
|
|
2361
2372
|
onOpenChange={setIsClearConfirmationOpen}
|
|
2362
2373
|
title="Clear all filters?"
|
|
@@ -185,6 +185,9 @@ describe('MobileTokenizer (Lab, single-sheet flow)', () => {
|
|
|
185
185
|
let discard = await screen.findByRole('alertdialog', {
|
|
186
186
|
name: 'Discard changes?',
|
|
187
187
|
});
|
|
188
|
+
// A scrim press starts and ends on the dialog itself.
|
|
189
|
+
fireEvent.pointerDown(discard);
|
|
190
|
+
fireEvent.pointerUp(discard);
|
|
188
191
|
fireEvent.click(discard);
|
|
189
192
|
await waitFor(() =>
|
|
190
193
|
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(),
|
|
@@ -1245,6 +1248,7 @@ describe('MobileTokenizer Core-sheet parity', () => {
|
|
|
1245
1248
|
);
|
|
1246
1249
|
}
|
|
1247
1250
|
render(<Flow />);
|
|
1251
|
+
const trigger = screen.getByRole('button', {name: /Tags/});
|
|
1248
1252
|
const sheet = await openSheet();
|
|
1249
1253
|
// Pending from open until the bootstrap settles.
|
|
1250
1254
|
expect(within(sheet).getByRole('img', {name: 'Loading'})).toBeTruthy();
|
|
@@ -1261,11 +1265,8 @@ describe('MobileTokenizer Core-sheet parity', () => {
|
|
|
1261
1265
|
// The field becomes disabled while open: the sheet closes and the query
|
|
1262
1266
|
// is kept; re-enabled and reopened, it starts from "en", settled.
|
|
1263
1267
|
fireEvent.click(screen.getByRole('button', {name: 'Toggle'}));
|
|
1264
|
-
expect(
|
|
1265
|
-
|
|
1266
|
-
'false',
|
|
1267
|
-
);
|
|
1268
|
-
act(() => screen.getByRole('button', {name: /Tags/}).focus());
|
|
1268
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
1269
|
+
act(() => trigger.focus());
|
|
1269
1270
|
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
|
1270
1271
|
fireEvent.click(screen.getByRole('button', {name: 'Toggle'}));
|
|
1271
1272
|
const reopened = await openSheet();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file MobileTokenizer.tsx
|
|
7
7
|
* @input Public @astryxdesign/core components and hooks, plus private Lab
|
|
8
|
-
*
|
|
8
|
+
* touch-sheet confirmation and search-drag dismissal from Core's internal entry
|
|
9
9
|
* @output Exports MobileTokenizer — Lab prototype of the touch Tokenizer
|
|
10
10
|
* flow with the inline-compatible closed trigger, a searchable draft-selection
|
|
11
11
|
* sheet, persistent Clear all / Save footer, dirty-dismiss confirmation,
|
|
@@ -72,8 +72,13 @@ import {
|
|
|
72
72
|
sizeVars,
|
|
73
73
|
spacingVars,
|
|
74
74
|
} from '@astryxdesign/core/theme/tokens.stylex';
|
|
75
|
-
import {
|
|
76
|
-
|
|
75
|
+
import {
|
|
76
|
+
SheetConfirmation,
|
|
77
|
+
hasSameSelection,
|
|
78
|
+
nameSheetDraftChange,
|
|
79
|
+
useDismissSearchOnTouchDrag,
|
|
80
|
+
type SheetDraftChange,
|
|
81
|
+
} from '@astryxdesign/core/internal/touch-sheet';
|
|
77
82
|
|
|
78
83
|
export type MobileTokenizerChange<T extends SearchableItem> =
|
|
79
84
|
| {item: T; type: 'add'}
|
|
@@ -130,18 +135,6 @@ const LIST_LOAD_MORE_THRESHOLD_PX = 200;
|
|
|
130
135
|
// border = 3px. The same number is the row gap, so wrapped rows read evenly.
|
|
131
136
|
const TOKEN_ROW_INSET = `calc(${spacingVars['--spacing-1']} - 1px)`;
|
|
132
137
|
|
|
133
|
-
type SheetDraftChange<T> = {
|
|
134
|
-
item: T;
|
|
135
|
-
type: 'add' | 'create' | 'remove';
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
function hasSameSelection<T extends SearchableItem>(a: T[], b: T[]): boolean {
|
|
139
|
-
return (
|
|
140
|
-
a.length === b.length &&
|
|
141
|
-
a.every(item => b.some(candidate => candidate.id === item.id))
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
138
|
const styles = stylex.create({
|
|
146
139
|
// The closed trigger is the inline Tokenizer field's own control box, as a
|
|
147
140
|
// button — the same `inputWrapperStyles.base` box Core's sheet trigger
|
|
@@ -1018,39 +1011,15 @@ function MobileTokenizerSheetContent<T extends SearchableItem>({
|
|
|
1018
1011
|
if (isDraftOverMax) {
|
|
1019
1012
|
return;
|
|
1020
1013
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
lastDraftChange != null &&
|
|
1031
|
-
(lastDraftChange.type === 'remove'
|
|
1032
|
-
? valueAtOpen.some(item => item.id === lastDraftChange.item.id) &&
|
|
1033
|
-
!draftValue.some(item => item.id === lastDraftChange.item.id)
|
|
1034
|
-
: draftValue.some(item => item.id === lastDraftChange.item.id) &&
|
|
1035
|
-
!valueAtOpen.some(item => item.id === lastDraftChange.item.id));
|
|
1036
|
-
let change: SheetDraftChange<T> | null = null;
|
|
1037
|
-
if (lastChangeIsNet) {
|
|
1038
|
-
change = lastDraftChange;
|
|
1039
|
-
} else if (lastDraftChange?.type === 'remove' && removed != null) {
|
|
1040
|
-
change = {item: removed, type: 'remove'};
|
|
1041
|
-
} else if (added != null) {
|
|
1042
|
-
const type: SheetDraftChange<T>['type'] =
|
|
1043
|
-
lastDraftChange?.item.id === added.id &&
|
|
1044
|
-
lastDraftChange.type === 'create'
|
|
1045
|
-
? 'create'
|
|
1046
|
-
: 'add';
|
|
1047
|
-
change = {item: added, type};
|
|
1048
|
-
} else if (removed != null) {
|
|
1049
|
-
change = {item: removed, type: 'remove'};
|
|
1050
|
-
}
|
|
1051
|
-
if (change != null) {
|
|
1052
|
-
onCommit([...draftValue], change);
|
|
1053
|
-
}
|
|
1014
|
+
const change = isDraftDirty
|
|
1015
|
+
? nameSheetDraftChange(
|
|
1016
|
+
valueAtOpen,
|
|
1017
|
+
draftValue,
|
|
1018
|
+
lastDraftChangeRef.current,
|
|
1019
|
+
)
|
|
1020
|
+
: null;
|
|
1021
|
+
if (change != null) {
|
|
1022
|
+
onCommit([...draftValue], change);
|
|
1054
1023
|
}
|
|
1055
1024
|
onCloseSheet();
|
|
1056
1025
|
};
|
|
@@ -1058,7 +1027,12 @@ function MobileTokenizerSheetContent<T extends SearchableItem>({
|
|
|
1058
1027
|
const searchLabel = t('@astryx.tokenizer.search', {label});
|
|
1059
1028
|
const noResultsTitle =
|
|
1060
1029
|
emptySearchResultsText ?? t('@astryx.typeahead.emptySearchResults');
|
|
1061
|
-
|
|
1030
|
+
// A drag moves focus to the sheet's panel, never to the page body behind
|
|
1031
|
+
// the open modal.
|
|
1032
|
+
const listDragProps = useDismissSearchOnTouchDrag(
|
|
1033
|
+
() => [searchRef.current],
|
|
1034
|
+
() => searchRef.current?.closest<HTMLElement>('[tabindex="-1"]'),
|
|
1035
|
+
);
|
|
1062
1036
|
|
|
1063
1037
|
return (
|
|
1064
1038
|
<div {...stylex.props(styles.sheetBody)}>
|
|
@@ -1245,7 +1219,7 @@ function MobileTokenizerSheetContent<T extends SearchableItem>({
|
|
|
1245
1219
|
/>
|
|
1246
1220
|
</div>
|
|
1247
1221
|
{isClearConfirmationOpen && (
|
|
1248
|
-
<
|
|
1222
|
+
<SheetConfirmation
|
|
1249
1223
|
isOpen
|
|
1250
1224
|
onOpenChange={next => {
|
|
1251
1225
|
if (!next) {
|
|
@@ -1270,7 +1244,7 @@ function MobileTokenizerSheetContent<T extends SearchableItem>({
|
|
|
1270
1244
|
/>
|
|
1271
1245
|
)}
|
|
1272
1246
|
{isDiscardConfirmationOpen && (
|
|
1273
|
-
<
|
|
1247
|
+
<SheetConfirmation
|
|
1274
1248
|
isOpen
|
|
1275
1249
|
onOpenChange={next => {
|
|
1276
1250
|
if (!next) {
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file InfoTip.tsx
|
|
3
|
-
* @input Uses React, stylex, Tooltip + Icon from @astryxdesign/core, color/spacing/radius/duration/ease tokens
|
|
4
|
-
* @output Exports InfoTip component, InfoTipProps, InfoTipSize types
|
|
5
|
-
* @position Lab experiment (RFC meta/astryx#3349); core implementation consumed by index.ts
|
|
6
|
-
*
|
|
7
|
-
* SYNC: When modified, update these files to stay in sync:
|
|
8
|
-
* - /packages/lab/src/InfoTip/InfoTip.doc.mjs (props table, features)
|
|
9
|
-
* - /packages/lab/src/InfoTip/InfoTip.test.tsx (tests for new/changed behavior)
|
|
10
|
-
* - /packages/lab/src/InfoTip/index.ts (exports if types change)
|
|
11
|
-
*/
|
|
12
|
-
import { type ReactNode } from 'react';
|
|
13
|
-
import { type IconSize } from '@astryxdesign/core/Icon';
|
|
14
|
-
/**
|
|
15
|
-
* Size of the info icon. Maps 1:1 to Icon sizes
|
|
16
|
-
* (xsm: 12px, sm: 16px, md: 20px, lg: 24px).
|
|
17
|
-
*/
|
|
18
|
-
export type InfoTipSize = IconSize;
|
|
19
|
-
export interface InfoTipProps {
|
|
20
|
-
/**
|
|
21
|
-
* Content to display in the tooltip.
|
|
22
|
-
* Typically short, non-interactive text. Mirrors Tooltip's `content` prop.
|
|
23
|
-
*/
|
|
24
|
-
content: ReactNode;
|
|
25
|
-
/**
|
|
26
|
-
* Accessible name for the trigger button.
|
|
27
|
-
* @default 'More information'
|
|
28
|
-
*/
|
|
29
|
-
label?: string;
|
|
30
|
-
/**
|
|
31
|
-
* Size of the info icon.
|
|
32
|
-
* @default 'sm'
|
|
33
|
-
*/
|
|
34
|
-
size?: InfoTipSize;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* An inline info-icon help affordance: a small "i" button that reveals a
|
|
38
|
-
* tooltip on hover, keyboard focus, and tap. Use it next to labels, values,
|
|
39
|
-
* and metrics for permission notes, metric definitions, and field help.
|
|
40
|
-
*
|
|
41
|
-
* The value over hand-composing Icon inside Tooltip is the pre-wired
|
|
42
|
-
* accessible trigger: a real button with an aria-label, Tab-reachable,
|
|
43
|
-
* tooltip on hover AND focus, and Escape dismissal.
|
|
44
|
-
*
|
|
45
|
-
* Composed entirely from core primitives (Tooltip + Icon); the info icon
|
|
46
|
-
* resolves from the global icon registry, so themes can override it.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```
|
|
50
|
-
* <InfoTip content="Editors can change this field; viewers cannot." />
|
|
51
|
-
* <InfoTip content="30-day rolling average." label="About this metric" />
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export declare function InfoTip({ content, label, size, }: InfoTipProps): ReactNode;
|
|
55
|
-
export declare namespace InfoTip {
|
|
56
|
-
var displayName: string;
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=InfoTip.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InfoTip.d.ts","sourceRoot":"","sources":["../../src/InfoTip/InfoTip.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAGrC,OAAO,EAAO,KAAK,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAW5D;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEnC,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AA8BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,KAA0B,EAC1B,IAAW,GACZ,EAAE,YAAY,GAAG,SAAS,CAqB1B;yBAzBe,OAAO"}
|
package/dist/InfoTip/InfoTip.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
'use client';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @file InfoTip.tsx
|
|
7
|
-
* @input Uses React, stylex, Tooltip + Icon from @astryxdesign/core, color/spacing/radius/duration/ease tokens
|
|
8
|
-
* @output Exports InfoTip component, InfoTipProps, InfoTipSize types
|
|
9
|
-
* @position Lab experiment (RFC meta/astryx#3349); core implementation consumed by index.ts
|
|
10
|
-
*
|
|
11
|
-
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
-
* - /packages/lab/src/InfoTip/InfoTip.doc.mjs (props table, features)
|
|
13
|
-
* - /packages/lab/src/InfoTip/InfoTip.test.tsx (tests for new/changed behavior)
|
|
14
|
-
* - /packages/lab/src/InfoTip/index.ts (exports if types change)
|
|
15
|
-
*/
|
|
16
|
-
import * as stylex from '@stylexjs/stylex';
|
|
17
|
-
import { Icon } from '@astryxdesign/core/Icon';
|
|
18
|
-
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
|
19
|
-
import '@astryxdesign/core/theme/tokens.stylex';
|
|
20
|
-
import { colorVars, durationVars, easeVars, radiusVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
21
|
-
import { focusOutlineStyles } from '@astryxdesign/core/utils';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Size of the info icon. Maps 1:1 to Icon sizes
|
|
25
|
-
* (xsm: 12px, sm: 16px, md: 20px, lg: 24px).
|
|
26
|
-
*/
|
|
27
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
28
|
-
const styles = {
|
|
29
|
-
trigger: {
|
|
30
|
-
k1xSpc: "x3nfvp2",
|
|
31
|
-
kGNEyG: "x6s0dn4",
|
|
32
|
-
kjj79g: "xl56j7k",
|
|
33
|
-
kXLuUW: "xxymvpz",
|
|
34
|
-
kmVPX3: "x12nba7r",
|
|
35
|
-
kogj98: "x1ghz6dp",
|
|
36
|
-
ksu8eU: "xng3xce",
|
|
37
|
-
kWkggS: "xjbqb8w",
|
|
38
|
-
kaIpWk: "xjspbzw",
|
|
39
|
-
kkrTdU: "x1ypdohk x16khyan",
|
|
40
|
-
kMwMTN: "xv9yike x10ue8fs",
|
|
41
|
-
k1ekBW: "xt3l3uh",
|
|
42
|
-
kIyJzY: "xuedmi6",
|
|
43
|
-
kAMwcw: "xlr8y92",
|
|
44
|
-
$$css: true
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* An inline info-icon help affordance: a small "i" button that reveals a
|
|
50
|
-
* tooltip on hover, keyboard focus, and tap. Use it next to labels, values,
|
|
51
|
-
* and metrics for permission notes, metric definitions, and field help.
|
|
52
|
-
*
|
|
53
|
-
* The value over hand-composing Icon inside Tooltip is the pre-wired
|
|
54
|
-
* accessible trigger: a real button with an aria-label, Tab-reachable,
|
|
55
|
-
* tooltip on hover AND focus, and Escape dismissal.
|
|
56
|
-
*
|
|
57
|
-
* Composed entirely from core primitives (Tooltip + Icon); the info icon
|
|
58
|
-
* resolves from the global icon registry, so themes can override it.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```
|
|
62
|
-
* <InfoTip content="Editors can change this field; viewers cannot." />
|
|
63
|
-
* <InfoTip content="30-day rolling average." label="About this metric" />
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
export function InfoTip({
|
|
67
|
-
content,
|
|
68
|
-
label = 'More information',
|
|
69
|
-
size = 'sm'
|
|
70
|
-
}) {
|
|
71
|
-
// No local Escape handler: the trigger used to stopPropagation the press,
|
|
72
|
-
// which hid the tip but left the browser's close watcher to fire `cancel` on
|
|
73
|
-
// an enclosing Dialog, so one press took both (#5168). Tooltip's own entry in
|
|
74
|
-
// the shared dismissal stack takes it instead.
|
|
75
|
-
return /*#__PURE__*/_jsx(Tooltip, {
|
|
76
|
-
content: content
|
|
77
|
-
// The trigger is a real button, so Tooltip's `auto` touch rule would
|
|
78
|
-
// give the tap to the control and suppress the tooltip. Here the tooltip
|
|
79
|
-
// IS the control's only purpose, so the tap has to open it — otherwise
|
|
80
|
-
// an InfoTip's content is unreachable on a phone.
|
|
81
|
-
,
|
|
82
|
-
touchTrigger: "tap",
|
|
83
|
-
children: /*#__PURE__*/_jsx("button", {
|
|
84
|
-
type: "button",
|
|
85
|
-
"aria-label": label,
|
|
86
|
-
...stylex.props(focusOutlineStyles.focusVisible, styles.trigger),
|
|
87
|
-
children: /*#__PURE__*/_jsx(Icon, {
|
|
88
|
-
icon: "info",
|
|
89
|
-
size: size
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
InfoTip.displayName = 'InfoTip';
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface MobileSheetConfirmationProps {
|
|
2
|
-
isOpen: boolean;
|
|
3
|
-
onOpenChange: (isOpen: boolean) => void;
|
|
4
|
-
title: string;
|
|
5
|
-
description: string;
|
|
6
|
-
cancelLabel: string;
|
|
7
|
-
actionLabel: string;
|
|
8
|
-
onAction: () => void;
|
|
9
|
-
}
|
|
10
|
-
export declare function MobileSheetConfirmation({ isOpen, onOpenChange, title, description, cancelLabel, actionLabel, onAction, }: MobileSheetConfirmationProps): import("react").JSX.Element;
|
|
11
|
-
export {};
|
|
12
|
-
//# sourceMappingURL=MobileSheetConfirmation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MobileSheetConfirmation.d.ts","sourceRoot":"","sources":["../../src/internal/MobileSheetConfirmation.tsx"],"names":[],"mappings":"AA0BA,UAAU,4BAA4B;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAMD,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,YAAY,EACZ,KAAK,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,4BAA4B,+BAgD9B"}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
'use client';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @file MobileSheetConfirmation.tsx
|
|
7
|
-
* @input Public Core Dialog, layout, text and action primitives; caller-owned copy
|
|
8
|
-
* @output Private confirmation shared by MobileTokenizer and MobilePowerSearch
|
|
9
|
-
* @position Lab-only mobile sheet confirmation owner; not a public export
|
|
10
|
-
*
|
|
11
|
-
* Core AlertDialog fixes its purpose to form and its safe action to ghost.
|
|
12
|
-
* These Lab flows instead allow scrim dismissal and use separated, full-width
|
|
13
|
-
* large actions with the secondary safe action focused first. Keep that
|
|
14
|
-
* difference here, while preserving the existing alert-dialog theme target.
|
|
15
|
-
*/
|
|
16
|
-
import { useId } from 'react';
|
|
17
|
-
import * as stylex from '@stylexjs/stylex';
|
|
18
|
-
import { Button } from '@astryxdesign/core/Button';
|
|
19
|
-
import { Dialog } from '@astryxdesign/core/Dialog';
|
|
20
|
-
import { Heading } from '@astryxdesign/core/Heading';
|
|
21
|
-
import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
|
|
22
|
-
import { Stack } from '@astryxdesign/core/Stack';
|
|
23
|
-
import { Text } from '@astryxdesign/core/Text';
|
|
24
|
-
import { themeProps } from '@astryxdesign/core/utils';
|
|
25
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
26
|
-
const styles = {
|
|
27
|
-
action: {
|
|
28
|
-
kzqmXN: "xh8yej3",
|
|
29
|
-
$$css: true
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
export function MobileSheetConfirmation({
|
|
33
|
-
isOpen,
|
|
34
|
-
onOpenChange,
|
|
35
|
-
title,
|
|
36
|
-
description,
|
|
37
|
-
cancelLabel,
|
|
38
|
-
actionLabel,
|
|
39
|
-
onAction
|
|
40
|
-
}) {
|
|
41
|
-
const titleId = useId();
|
|
42
|
-
const descriptionId = useId();
|
|
43
|
-
return /*#__PURE__*/_jsx(Dialog, {
|
|
44
|
-
...themeProps('alert-dialog'),
|
|
45
|
-
isOpen: isOpen,
|
|
46
|
-
onOpenChange: onOpenChange,
|
|
47
|
-
purpose: "info",
|
|
48
|
-
role: "alertdialog",
|
|
49
|
-
"aria-labelledby": titleId,
|
|
50
|
-
"aria-describedby": descriptionId,
|
|
51
|
-
width: 400,
|
|
52
|
-
children: /*#__PURE__*/_jsx(Layout, {
|
|
53
|
-
content: /*#__PURE__*/_jsxs(LayoutContent, {
|
|
54
|
-
children: [/*#__PURE__*/_jsx(Heading, {
|
|
55
|
-
level: 2,
|
|
56
|
-
id: titleId,
|
|
57
|
-
children: title
|
|
58
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
59
|
-
type: "body",
|
|
60
|
-
color: "secondary",
|
|
61
|
-
id: descriptionId,
|
|
62
|
-
children: description
|
|
63
|
-
})]
|
|
64
|
-
}),
|
|
65
|
-
footer: /*#__PURE__*/_jsx(LayoutFooter, {
|
|
66
|
-
children: /*#__PURE__*/_jsxs(Stack, {
|
|
67
|
-
direction: "vertical",
|
|
68
|
-
gap: 4,
|
|
69
|
-
hAlign: "stretch",
|
|
70
|
-
children: [/*#__PURE__*/_jsx(Button, {
|
|
71
|
-
label: actionLabel,
|
|
72
|
-
variant: "destructive",
|
|
73
|
-
size: "lg",
|
|
74
|
-
onClick: onAction,
|
|
75
|
-
xstyle: styles.action
|
|
76
|
-
}), /*#__PURE__*/_jsx(Button, {
|
|
77
|
-
label: cancelLabel,
|
|
78
|
-
variant: "secondary",
|
|
79
|
-
size: "lg",
|
|
80
|
-
onClick: () => onOpenChange(false),
|
|
81
|
-
"data-autofocus": true,
|
|
82
|
-
xstyle: styles.action
|
|
83
|
-
})]
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
});
|
|
88
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file useDismissSearchOnTouchDrag.ts
|
|
3
|
-
* @input Caller-owned search inputs and touch events from a separate results list
|
|
4
|
-
* @output Private touch handlers shared by the Lab mobile search sheets
|
|
5
|
-
* @position One owner for results-drag keyboard dismissal; not a public export
|
|
6
|
-
*
|
|
7
|
-
* A direct vertical touch drag dismisses only a focused caller-owned search.
|
|
8
|
-
* Scroll events are deliberately not observed: filtering can clamp scrollTop
|
|
9
|
-
* and fire scroll while someone is typing. Small moves, taps and multi-touch
|
|
10
|
-
* gestures leave focus alone. The hook acquires no external resource.
|
|
11
|
-
*/
|
|
12
|
-
import { type TouchEvent } from 'react';
|
|
13
|
-
type SearchInput = HTMLInputElement | null | undefined;
|
|
14
|
-
export declare function useDismissSearchOnTouchDrag(getSearchInputs: () => readonly SearchInput[]): {
|
|
15
|
-
onTouchStart: (event: TouchEvent<HTMLElement>) => void;
|
|
16
|
-
onTouchMove: (event: TouchEvent<HTMLElement>) => void;
|
|
17
|
-
onTouchEnd: () => void;
|
|
18
|
-
onTouchCancel: () => void;
|
|
19
|
-
};
|
|
20
|
-
export {};
|
|
21
|
-
//# sourceMappingURL=useDismissSearchOnTouchDrag.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDismissSearchOnTouchDrag.d.ts","sourceRoot":"","sources":["../../src/internal/useDismissSearchOnTouchDrag.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,EAAS,KAAK,UAAU,EAAC,MAAM,OAAO,CAAC;AAI9C,KAAK,WAAW,GAAG,gBAAgB,GAAG,IAAI,GAAG,SAAS,CAAC;AAEvD,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,SAAS,WAAW,EAAE;0BAQrB,UAAU,CAAC,WAAW,CAAC;yBAIxB,UAAU,CAAC,WAAW,CAAC;;;EAqB/C"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
'use client';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @file useDismissSearchOnTouchDrag.ts
|
|
7
|
-
* @input Caller-owned search inputs and touch events from a separate results list
|
|
8
|
-
* @output Private touch handlers shared by the Lab mobile search sheets
|
|
9
|
-
* @position One owner for results-drag keyboard dismissal; not a public export
|
|
10
|
-
*
|
|
11
|
-
* A direct vertical touch drag dismisses only a focused caller-owned search.
|
|
12
|
-
* Scroll events are deliberately not observed: filtering can clamp scrollTop
|
|
13
|
-
* and fire scroll while someone is typing. Small moves, taps and multi-touch
|
|
14
|
-
* gestures leave focus alone. The hook acquires no external resource.
|
|
15
|
-
*/
|
|
16
|
-
import { useRef } from 'react';
|
|
17
|
-
const DRAG_THRESHOLD_PX = 8;
|
|
18
|
-
export function useDismissSearchOnTouchDrag(getSearchInputs) {
|
|
19
|
-
const startYRef = useRef(null);
|
|
20
|
-
const reset = () => {
|
|
21
|
-
startYRef.current = null;
|
|
22
|
-
};
|
|
23
|
-
return {
|
|
24
|
-
onTouchStart: event => {
|
|
25
|
-
startYRef.current = event.touches.length === 1 ? event.touches[0].clientY : null;
|
|
26
|
-
},
|
|
27
|
-
onTouchMove: event => {
|
|
28
|
-
if (event.touches.length !== 1) {
|
|
29
|
-
reset();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const startY = startYRef.current;
|
|
33
|
-
if (startY == null || Math.abs(event.touches[0].clientY - startY) < DRAG_THRESHOLD_PX) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
reset();
|
|
37
|
-
const focusedSearch = getSearchInputs().find(input => input != null && input === document.activeElement);
|
|
38
|
-
focusedSearch?.blur();
|
|
39
|
-
},
|
|
40
|
-
onTouchEnd: reset,
|
|
41
|
-
onTouchCancel: reset
|
|
42
|
-
};
|
|
43
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
|
-
|
|
5
|
-
export const docs = {
|
|
6
|
-
name: 'InfoTip',
|
|
7
|
-
displayName: 'Info Tip',
|
|
8
|
-
category: 'Feedback & Status',
|
|
9
|
-
keywords: ["info","tooltip","help","hint","affordance","icon","label","field","metric","definition"],
|
|
10
|
-
props: [
|
|
11
|
-
{
|
|
12
|
-
name: 'content',
|
|
13
|
-
type: 'ReactNode',
|
|
14
|
-
description: 'Content to display in the tooltip. Typically short, non-interactive text. Mirrors Tooltip\'s content prop.',
|
|
15
|
-
required: true,
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: 'label',
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'Accessible name for the trigger button.',
|
|
21
|
-
default: "'More information'",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: 'size',
|
|
25
|
-
type: "'xsm' | 'sm' | 'md' | 'lg'",
|
|
26
|
-
description: 'Size of the info icon (12px, 16px, 20px, 24px). Maps 1:1 to Icon sizes.',
|
|
27
|
-
default: "'sm'",
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
usage: {
|
|
31
|
-
description:
|
|
32
|
-
'An inline info-icon help affordance: a small "i" button that reveals a tooltip on hover, keyboard focus, and tap. Use it next to labels, values, and metrics for permission notes, metric definitions, and field help. Its value over hand-composing Icon inside Tooltip is the pre-wired accessible trigger: a real button with an aria-label, Tab-reachable, tooltip on hover AND focus AND tap, and Escape dismissal.',
|
|
33
|
-
bestPractices: [
|
|
34
|
-
{ guidance: true, description: 'Keep tooltip content concise, plain, and non-interactive; use Popover or HoverCard for links and buttons.' },
|
|
35
|
-
{ guidance: true, description: 'Override label when "More information" is too generic, e.g. "About this metric".' },
|
|
36
|
-
{ guidance: true, description: 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip touchTrigger to tap, so a tap opens the tooltip even though the trigger is a button, and a tap outside dismisses it.' },
|
|
37
|
-
{ guidance: false, description: 'Hand-compose Icon inside Tooltip for info affordances; the bare Icon is aria-hidden and unfocusable, so keyboard and screen-reader users never see it.' },
|
|
38
|
-
{ guidance: false, description: 'Use InfoTip for essential information users must see to complete a task; put that text inline instead.' },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
44
|
-
export const docsZh = {
|
|
45
|
-
name: 'InfoTip',
|
|
46
|
-
displayName: 'Info Tip',
|
|
47
|
-
props: [
|
|
48
|
-
{
|
|
49
|
-
name: 'content',
|
|
50
|
-
type: 'ReactNode',
|
|
51
|
-
description: '工具提示中显示的内容。通常为简短的非交互文本。与 Tooltip 的 content 属性一致。',
|
|
52
|
-
required: true,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: 'label',
|
|
56
|
-
type: 'string',
|
|
57
|
-
description: '触发按钮的无障碍名称。',
|
|
58
|
-
default: "'More information'",
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'size',
|
|
62
|
-
type: "'xsm' | 'sm' | 'md' | 'lg'",
|
|
63
|
-
description: '信息图标的尺寸(12px、16px、20px、24px)。与 Icon 尺寸一一对应。',
|
|
64
|
-
default: "'sm'",
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
usage: {
|
|
68
|
-
description:
|
|
69
|
-
'An inline info-icon help affordance: a small "i" button that reveals a tooltip on hover, keyboard focus, and tap. Use it next to labels, values, and metrics for permission notes, metric definitions, and field help.',
|
|
70
|
-
bestPractices: [
|
|
71
|
-
{ guidance: true, description: 'Keep tooltip content concise, plain, and non-interactive; use Popover or HoverCard for links and buttons.' },
|
|
72
|
-
{ guidance: true, description: 'Override label when "More information" is too generic, e.g. "About this metric".' },
|
|
73
|
-
{ guidance: true, description: 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip touchTrigger to tap, so a tap opens the tooltip even though the trigger is a button, and a tap outside dismisses it.' },
|
|
74
|
-
{ guidance: false, description: 'Hand-compose Icon inside Tooltip for info affordances; the bare Icon is aria-hidden and unfocusable, so keyboard and screen-reader users never see it.' },
|
|
75
|
-
{ guidance: false, description: 'Use InfoTip for essential information users must see to complete a task; put that text inline instead.' },
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
|
|
81
|
-
export const docsDense = {
|
|
82
|
-
description:
|
|
83
|
-
'Inline info-icon help affordance: accessible "i" button trigger pre-wired into a Tooltip.',
|
|
84
|
-
usage: {
|
|
85
|
-
description:
|
|
86
|
-
'A small "i" button that reveals a tooltip on hover, keyboard focus, and tap. Use next to labels, values, and metrics for permission notes, metric definitions, and field help. Pre-wired accessible trigger: real button, aria-label, Tab-reachable, tooltip on hover AND focus AND tap, Escape dismissal.',
|
|
87
|
-
bestPractices: [
|
|
88
|
-
{ guidance: true, description: 'Keep tooltip content concise, plain, and non-interactive; use Popover or HoverCard for links and buttons.' },
|
|
89
|
-
{ guidance: true, description: 'Override label when "More information" is too generic, e.g. "About this metric".' },
|
|
90
|
-
{ guidance: true, description: 'Rely on InfoTip rather than a hand-rolled info button on touch: it sets Tooltip touchTrigger to tap, so a tap opens the tooltip even though the trigger is a button, and a tap outside dismisses it.' },
|
|
91
|
-
{ guidance: false, description: 'Hand-compose Icon inside Tooltip for info affordances; the bare Icon is aria-hidden and unfocusable.' },
|
|
92
|
-
{ guidance: false, description: 'Use InfoTip for essential information users must see to complete a task; put that text inline instead.' },
|
|
93
|
-
],
|
|
94
|
-
},
|
|
95
|
-
propDescriptions: {
|
|
96
|
-
content: 'Tooltip content. Short, non-interactive text. Mirrors Tooltip content prop.',
|
|
97
|
-
label: "Accessible name for the trigger button. Default 'More information'.",
|
|
98
|
-
size: 'Info icon size (12/16/20/24px). Maps 1:1 to Icon sizes.',
|
|
99
|
-
},
|
|
100
|
-
};
|