@multiplatform.one/forms 7.67.0 → 7.69.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/cjs/AdaptivePopup/index.cjs +45 -5
- package/dist/cjs/AdaptivePopup/index.native.js +47 -5
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/IconButtonGroup.cjs +95 -0
- package/dist/cjs/Button/IconButtonGroup.native.js +101 -0
- package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -0
- package/dist/cjs/Button/glass.cjs +34 -0
- package/dist/cjs/Button/glass.native.js +55 -0
- package/dist/cjs/Button/glass.native.js.map +1 -0
- package/dist/cjs/Button/iconButtonGroupContext.cjs +33 -0
- package/dist/cjs/Button/iconButtonGroupContext.native.js +36 -0
- package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -0
- package/dist/cjs/Button/index.cjs +174 -30
- package/dist/cjs/Button/index.native.js +174 -29
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Button/mediaPlate.cjs +31 -0
- package/dist/cjs/Button/mediaPlate.native.js +34 -0
- package/dist/cjs/Button/mediaPlate.native.js.map +1 -0
- package/dist/cjs/ContextualSaveBar/index.cjs +6 -8
- package/dist/cjs/ContextualSaveBar/index.native.js +6 -8
- package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/cjs/DescriptionList/index.cjs +0 -1
- package/dist/cjs/DescriptionList/index.native.js +0 -1
- package/dist/cjs/DescriptionList/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/index.native.js +21 -16
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/BarcodeScanner.cjs +7 -8
- package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +7 -8
- package/dist/cjs/fields/Barcode/BarcodeScanner.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/index.cjs +86 -60
- package/dist/cjs/fields/FileUpload/index.native.js +99 -73
- package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/index.cjs +7 -8
- package/dist/cjs/fields/Geolocation/index.native.js +7 -8
- package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
- package/dist/cjs/fields/ImageField/index.cjs +4 -6
- package/dist/cjs/fields/ImageField/index.native.js +4 -6
- package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +20 -28
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/index.cjs +11 -1
- package/dist/cjs/index.native.js +10 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/shared/colorRamps.cjs +0 -8
- package/dist/cjs/shared/colorRamps.native.js +0 -8
- package/dist/cjs/shared/colorRamps.native.js.map +1 -1
- package/dist/cjs/standardActions.cjs +117 -0
- package/dist/cjs/standardActions.native.js +120 -0
- package/dist/cjs/standardActions.native.js.map +1 -0
- package/dist/esm/AdaptivePopup/index.mjs +45 -5
- package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js +47 -5
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/IconButtonGroup.mjs +69 -0
- package/dist/esm/Button/IconButtonGroup.mjs.map +1 -0
- package/dist/esm/Button/IconButtonGroup.native.js +72 -0
- package/dist/esm/Button/IconButtonGroup.native.js.map +1 -0
- package/dist/esm/Button/glass.mjs +8 -0
- package/dist/esm/Button/glass.mjs.map +1 -0
- package/dist/esm/Button/glass.native.js +26 -0
- package/dist/esm/Button/glass.native.js.map +1 -0
- package/dist/esm/Button/iconButtonGroupContext.mjs +7 -0
- package/dist/esm/Button/iconButtonGroupContext.mjs.map +1 -0
- package/dist/esm/Button/iconButtonGroupContext.native.js +7 -0
- package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -0
- package/dist/esm/Button/index.mjs +166 -30
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +167 -30
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Button/mediaPlate.mjs +6 -0
- package/dist/esm/Button/mediaPlate.mjs.map +1 -0
- package/dist/esm/Button/mediaPlate.native.js +6 -0
- package/dist/esm/Button/mediaPlate.native.js.map +1 -0
- package/dist/esm/ContextualSaveBar/index.mjs +7 -9
- package/dist/esm/ContextualSaveBar/index.mjs.map +1 -1
- package/dist/esm/ContextualSaveBar/index.native.js +7 -9
- package/dist/esm/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/esm/DescriptionList/index.mjs +0 -1
- package/dist/esm/DescriptionList/index.mjs.map +1 -1
- package/dist/esm/DescriptionList/index.native.js +0 -1
- package/dist/esm/DescriptionList/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +22 -17
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/fields/Barcode/BarcodeScanner.mjs +8 -9
- package/dist/esm/fields/Barcode/BarcodeScanner.mjs.map +1 -1
- package/dist/esm/fields/Barcode/BarcodeScanner.native.js +8 -9
- package/dist/esm/fields/Barcode/BarcodeScanner.native.js.map +1 -1
- package/dist/esm/fields/FileUpload/index.mjs +87 -61
- package/dist/esm/fields/FileUpload/index.mjs.map +1 -1
- package/dist/esm/fields/FileUpload/index.native.js +100 -74
- package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/index.mjs +8 -9
- package/dist/esm/fields/Geolocation/index.mjs.map +1 -1
- package/dist/esm/fields/Geolocation/index.native.js +8 -9
- package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
- package/dist/esm/fields/ImageField/index.mjs +6 -8
- package/dist/esm/fields/ImageField/index.mjs.map +1 -1
- package/dist/esm/fields/ImageField/index.native.js +6 -8
- package/dist/esm/fields/ImageField/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +20 -28
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/index.mjs +3 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/shared/colorRamps.mjs +0 -8
- package/dist/esm/shared/colorRamps.mjs.map +1 -1
- package/dist/esm/shared/colorRamps.native.js +0 -8
- package/dist/esm/shared/colorRamps.native.js.map +1 -1
- package/dist/esm/standardActions.mjs +91 -0
- package/dist/esm/standardActions.mjs.map +1 -0
- package/dist/esm/standardActions.native.js +91 -0
- package/dist/esm/standardActions.native.js.map +1 -0
- package/dist/jsx/AdaptivePopup/index.mjs +45 -5
- package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
- package/dist/jsx/AdaptivePopup/index.native.js +47 -5
- package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.mjs +69 -0
- package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -0
- package/dist/jsx/Button/IconButtonGroup.native.js +101 -0
- package/dist/jsx/Button/IconButtonGroup.native.js.map +1 -0
- package/dist/jsx/Button/glass.mjs +8 -0
- package/dist/jsx/Button/glass.mjs.map +1 -0
- package/dist/jsx/Button/glass.native.js +55 -0
- package/dist/jsx/Button/glass.native.js.map +1 -0
- package/dist/jsx/Button/iconButtonGroupContext.mjs +7 -0
- package/dist/jsx/Button/iconButtonGroupContext.mjs.map +1 -0
- package/dist/jsx/Button/iconButtonGroupContext.native.js +36 -0
- package/dist/jsx/Button/iconButtonGroupContext.native.js.map +1 -0
- package/dist/jsx/Button/index.mjs +166 -30
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +174 -29
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/Button/mediaPlate.mjs +6 -0
- package/dist/jsx/Button/mediaPlate.mjs.map +1 -0
- package/dist/jsx/Button/mediaPlate.native.js +34 -0
- package/dist/jsx/Button/mediaPlate.native.js.map +1 -0
- package/dist/jsx/ContextualSaveBar/index.mjs +7 -9
- package/dist/jsx/ContextualSaveBar/index.mjs.map +1 -1
- package/dist/jsx/ContextualSaveBar/index.native.js +6 -8
- package/dist/jsx/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/jsx/DescriptionList/index.mjs +0 -1
- package/dist/jsx/DescriptionList/index.mjs.map +1 -1
- package/dist/jsx/DescriptionList/index.native.js +0 -1
- package/dist/jsx/DescriptionList/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +21 -16
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/fields/Barcode/BarcodeScanner.mjs +8 -9
- package/dist/jsx/fields/Barcode/BarcodeScanner.mjs.map +1 -1
- package/dist/jsx/fields/Barcode/BarcodeScanner.native.js +7 -8
- package/dist/jsx/fields/Barcode/BarcodeScanner.native.js.map +1 -1
- package/dist/jsx/fields/FileUpload/index.mjs +87 -61
- package/dist/jsx/fields/FileUpload/index.mjs.map +1 -1
- package/dist/jsx/fields/FileUpload/index.native.js +99 -73
- package/dist/jsx/fields/FileUpload/index.native.js.map +1 -1
- package/dist/jsx/fields/Geolocation/index.mjs +8 -9
- package/dist/jsx/fields/Geolocation/index.mjs.map +1 -1
- package/dist/jsx/fields/Geolocation/index.native.js +7 -8
- package/dist/jsx/fields/Geolocation/index.native.js.map +1 -1
- package/dist/jsx/fields/ImageField/index.mjs +6 -8
- package/dist/jsx/fields/ImageField/index.mjs.map +1 -1
- package/dist/jsx/fields/ImageField/index.native.js +4 -6
- package/dist/jsx/fields/ImageField/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +20 -28
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +20 -28
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/index.js +3 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +10 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/colorRamps.mjs +0 -8
- package/dist/jsx/shared/colorRamps.mjs.map +1 -1
- package/dist/jsx/shared/colorRamps.native.js +0 -8
- package/dist/jsx/shared/colorRamps.native.js.map +1 -1
- package/dist/jsx/standardActions.mjs +91 -0
- package/dist/jsx/standardActions.mjs.map +1 -0
- package/dist/jsx/standardActions.native.js +120 -0
- package/dist/jsx/standardActions.native.js.map +1 -0
- package/package.json +12 -8
- package/src/AdaptivePopup/AdaptivePopup.spec.tsx +44 -0
- package/src/AdaptivePopup/AdaptivePopup.stories.tsx +12 -32
- package/src/AdaptivePopup/index.tsx +65 -13
- package/src/Button/Button.disabledGlyph.spec.tsx +111 -0
- package/src/Button/Button.iconButton.spec.tsx +341 -0
- package/src/Button/Button.stories.tsx +179 -2
- package/src/Button/IconButtonGroup.spec.tsx +126 -0
- package/src/Button/IconButtonGroup.tsx +87 -0
- package/src/Button/glass.native.ts +51 -0
- package/src/Button/glass.ts +33 -0
- package/src/Button/iconButtonGroupContext.ts +14 -0
- package/src/Button/index.tsx +314 -42
- package/src/Button/mediaPlate.ts +9 -0
- package/src/ContextualSaveBar/ContextualSaveBar.spec.tsx +3 -1
- package/src/ContextualSaveBar/ContextualSaveBar.stories.tsx +2 -2
- package/src/ContextualSaveBar/index.tsx +7 -10
- package/src/ControlGroup/ControlGroup.stories.tsx +3 -2
- package/src/DescriptionList/index.tsx +0 -1
- package/src/ErrorSummary.stories.tsx +2 -1
- package/src/FieldLayout.stories.tsx +3 -2
- package/src/FloatingPanel/FloatingPanel.pointMenu.native.spec.tsx +69 -0
- package/src/FloatingPanel/FloatingPanel.stories.tsx +6 -10
- package/src/FloatingPanel/index.native.tsx +25 -4
- package/src/Form/Form.stories.tsx +6 -5
- package/src/errorSummaryStories.spec.tsx +4 -4
- package/src/fields/Barcode/BarcodeScanner.native.tsx +10 -9
- package/src/fields/Barcode/BarcodeScanner.tsx +10 -9
- package/src/fields/Checkbox/Checkbox.stories.tsx +3 -3
- package/src/fields/Checkbox/CheckboxGroup.stories.tsx +2 -2
- package/src/fields/ColorPicker/ColorPicker.stories.tsx +2 -2
- package/src/fields/Combobox/Combobox.stories.tsx +2 -2
- package/src/fields/DatePicker/DatePicker.stories.tsx +2 -1
- package/src/fields/DatePicker/DateRangePicker.stories.tsx +2 -2
- package/src/fields/DatePicker/DatetimePicker.stories.tsx +2 -1
- package/src/fields/DatePicker/MonthPicker.stories.tsx +2 -2
- package/src/fields/DatePicker/MultiDatePicker.stories.tsx +2 -2
- package/src/fields/FileUpload/FileUpload.spec.tsx +14 -5
- package/src/fields/FileUpload/FileUpload.stories.tsx +2 -2
- package/src/fields/FileUpload/index.tsx +103 -77
- package/src/fields/Geolocation/index.tsx +12 -7
- package/src/fields/ImageField/ImageField.spec.tsx +5 -5
- package/src/fields/ImageField/ImageField.stories.tsx +2 -2
- package/src/fields/ImageField/index.tsx +6 -9
- package/src/fields/Input/Input.stories.tsx +2 -2
- package/src/fields/MentionInput/MentionInput.stories.tsx +2 -1
- package/src/fields/Progress/Progress.stories.tsx +2 -1
- package/src/fields/RadioGroup/RadioGroup.stories.tsx +2 -1
- package/src/fields/Signature/Signature.stories.tsx +2 -2
- package/src/fields/Stepper/Stepper.stories.tsx +2 -2
- package/src/fields/TextArea/TextArea.stories.tsx +2 -2
- package/src/fields/ToggleGroup/ToggleGroup.stories.tsx +2 -2
- package/src/fields/nativeDateTime/OsDateTimeSheet.presentation.spec.tsx +6 -5
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +24 -12
- package/src/index.ts +18 -2
- package/src/shared/colorRamps.ts +1 -11
- package/src/standardActions.ts +96 -0
- package/types/AdaptivePopup/index.d.ts +13 -1
- package/types/AdaptivePopup/index.d.ts.map +1 -1
- package/types/Button/IconButtonGroup.d.ts +22 -0
- package/types/Button/IconButtonGroup.d.ts.map +1 -0
- package/types/Button/glass.d.ts +26 -0
- package/types/Button/glass.d.ts.map +1 -0
- package/types/Button/glass.native.d.ts +17 -0
- package/types/Button/glass.native.d.ts.map +1 -0
- package/types/Button/iconButtonGroupContext.d.ts +9 -0
- package/types/Button/iconButtonGroupContext.d.ts.map +1 -0
- package/types/Button/index.d.ts +55 -2
- package/types/Button/index.d.ts.map +1 -1
- package/types/Button/mediaPlate.d.ts +10 -0
- package/types/Button/mediaPlate.d.ts.map +1 -0
- package/types/ContextualSaveBar/index.d.ts.map +1 -1
- package/types/DescriptionList/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +2 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/fields/Barcode/BarcodeScanner.d.ts.map +1 -1
- package/types/fields/Barcode/BarcodeScanner.native.d.ts.map +1 -1
- package/types/fields/FileUpload/index.d.ts.map +1 -1
- package/types/fields/Geolocation/index.d.ts.map +1 -1
- package/types/fields/ImageField/index.d.ts.map +1 -1
- package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
- package/types/index.d.ts +4 -2
- package/types/index.d.ts.map +1 -1
- package/types/shared/colorRamps.d.ts +1 -11
- package/types/shared/colorRamps.d.ts.map +1 -1
- package/types/standardActions.d.ts +24 -0
- package/types/standardActions.d.ts.map +1 -0
package/src/Button/index.tsx
CHANGED
|
@@ -72,6 +72,9 @@ import { formatActionLabel } from "../actionLabel";
|
|
|
72
72
|
import { formButtonColors, formCommonColors } from "../shared/colorRamps";
|
|
73
73
|
import { warnBareDisabled, warnPositiveTabIndex, warnSizeRecipeEscape } from "../shared/devWarn";
|
|
74
74
|
import { useControlGrouped } from "../shared/groupContext";
|
|
75
|
+
import { loadGlassView } from "./glass";
|
|
76
|
+
import { MEDIA_PLATE_WEB_STYLE } from "./mediaPlate";
|
|
77
|
+
import { useIconButtonGroupGround } from "./iconButtonGroupContext";
|
|
75
78
|
|
|
76
79
|
/**
|
|
77
80
|
* Coordinated size recipe (Polaris slim/medium/large, Primer s/m/l, Linear
|
|
@@ -382,6 +385,85 @@ export interface ButtonProps extends Omit<
|
|
|
382
385
|
* button that paints a frame, while selected, or disabled.
|
|
383
386
|
*/
|
|
384
387
|
accentInk?: boolean;
|
|
388
|
+
/**
|
|
389
|
+
* LC-176 ICON-BUTTON: an icon-only button is a bare glyph in the bar tint,
|
|
390
|
+
* no fill and no border, on a 44pt target. `plate="media"` is the one
|
|
391
|
+
* plated case: a translucent material circle for a control floating over
|
|
392
|
+
* an image or video, where a bare glyph would be lost on the picture.
|
|
393
|
+
* `plate="primary"` is the overlay's one confirm (LC-260): an accent-filled
|
|
394
|
+
* circle with an on-fill glyph, the iOS 26 sheet's checkmark.
|
|
395
|
+
*/
|
|
396
|
+
plate?: "media" | "primary";
|
|
397
|
+
/**
|
|
398
|
+
* LC-176: an icon button inline with a value (copy beside an address): the
|
|
399
|
+
* glyph is sized to the text, tinted, 8pt after it, and keeps the 44pt
|
|
400
|
+
* target through slop rather than a box that would grow the line.
|
|
401
|
+
*/
|
|
402
|
+
inline?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* LC-176: the glyph's stroke weight, matched to the text beside it. Phosphor
|
|
405
|
+
* regular for a tinted glyph, bold for a neutral one; never thin or light.
|
|
406
|
+
*/
|
|
407
|
+
glyphWeight?: "regular" | "bold";
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** LC-176: a pressed icon button dims on native, the iOS bar-button highlight. */
|
|
411
|
+
export const ICON_BUTTON_PRESS_OPACITY = 0.35;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* LC-176: an icon button's glyph floor. The glyph itself is sized to its row
|
|
415
|
+
* (`iconButtonGlyphPx`), so it is never smaller than a 44pt target's glyph.
|
|
416
|
+
*/
|
|
417
|
+
export const ICON_BUTTON_GLYPH_PX = 24;
|
|
418
|
+
|
|
419
|
+
/** LC-176: the content glyph's floor; the same bold, row-sized glyph. */
|
|
420
|
+
export const ICON_BUTTON_NEUTRAL_GLYPH_PX = 24;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Phosphor draws a glyph's ink across about 208 of its 256 units, so a glyph
|
|
424
|
+
* whose ink is as tall as a text line is that line over this ratio.
|
|
425
|
+
*/
|
|
426
|
+
const PHOSPHOR_INK_RATIO = 208 / 256;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* LC-176: an icon button's glyph is as tall in ink as the row's text line
|
|
430
|
+
* (YouTube's and the Phone app's bar glyphs), never a small glyph in a big
|
|
431
|
+
* target.
|
|
432
|
+
*/
|
|
433
|
+
export function iconButtonGlyphPx(rowLinePx: number | undefined): number {
|
|
434
|
+
if (rowLinePx === undefined) return ICON_BUTTON_GLYPH_PX;
|
|
435
|
+
return Math.max(ICON_BUTTON_GLYPH_PX, Math.round(rowLinePx / PHOSPHOR_INK_RATIO));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* LC-176: a glyph on a circle the caller asked for (`circular`, `outlined`)
|
|
440
|
+
* fills this share of the circle, the iOS Settings and Contacts leading-icon
|
|
441
|
+
* ratio, never a small glyph lost in a big plate.
|
|
442
|
+
*/
|
|
443
|
+
export const ICON_BUTTON_CIRCLE_GLYPH_RATIO = 0.58;
|
|
444
|
+
|
|
445
|
+
/** LC-176: an inline glyph is the text's size; Phosphor's 256 grid pads ~12%. */
|
|
446
|
+
const INLINE_GLYPH_RATIO = 1.2;
|
|
447
|
+
|
|
448
|
+
function withGlyphWeight(
|
|
449
|
+
icon: IconLike | undefined,
|
|
450
|
+
size: number,
|
|
451
|
+
weight: "regular" | "bold",
|
|
452
|
+
): IconLike | undefined {
|
|
453
|
+
if (icon == null || icon === false) return icon;
|
|
454
|
+
if (isValidElement<{ size?: number; weight?: string }>(icon)) {
|
|
455
|
+
return cloneElement(icon, {
|
|
456
|
+
...(icon.props.size == null ? { size } : undefined),
|
|
457
|
+
...(icon.props.weight == null ? { weight } : undefined),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (typeof icon === "function" || (typeof icon === "object" && "$$typeof" in icon)) {
|
|
461
|
+
return createElement(icon as ComponentType<{ size?: number; weight?: string }>, {
|
|
462
|
+
size,
|
|
463
|
+
weight,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return icon;
|
|
385
467
|
}
|
|
386
468
|
|
|
387
469
|
export function Button({
|
|
@@ -404,10 +486,13 @@ export function Button({
|
|
|
404
486
|
iconAfter,
|
|
405
487
|
selected,
|
|
406
488
|
nested = false,
|
|
407
|
-
circular,
|
|
489
|
+
circular: circularProp,
|
|
408
490
|
chromeless,
|
|
409
491
|
edge,
|
|
410
492
|
accentInk,
|
|
493
|
+
plate,
|
|
494
|
+
inline,
|
|
495
|
+
glyphWeight,
|
|
411
496
|
verb,
|
|
412
497
|
noun,
|
|
413
498
|
actionRole,
|
|
@@ -465,8 +550,9 @@ export function Button({
|
|
|
465
550
|
(s) => ({ isValid: s.isValid ?? true }),
|
|
466
551
|
);
|
|
467
552
|
|
|
553
|
+
const primaryPlate = plate === "primary";
|
|
468
554
|
const intent =
|
|
469
|
-
(accent && "accent") ||
|
|
555
|
+
((accent || primaryPlate) && "accent") ||
|
|
470
556
|
(error && "error") ||
|
|
471
557
|
(warning && "warning") ||
|
|
472
558
|
(success && "success") ||
|
|
@@ -486,12 +572,45 @@ export function Button({
|
|
|
486
572
|
const hostAccentInk = useAccentOnSurface();
|
|
487
573
|
const hostIdentityInk = useIdentityInkOnSurface();
|
|
488
574
|
const grouped = useControlGrouped();
|
|
575
|
+
// LC-176 ICON-BUTTON: with no label, the button is its glyph. It paints no
|
|
576
|
+
// plate at rest and takes the bar tint, unless the caller asked for a frame
|
|
577
|
+
// (`outlined`, `circular`, `chromeless={false}`) or a segment owns it.
|
|
578
|
+
const labelled = verb != null || (children != null && children !== false && children !== "");
|
|
579
|
+
const iconOnly = !labelled && Boolean(icon || iconAfter);
|
|
580
|
+
// LC-176: `circular chromeless` paints no plate, so an icon-only one is an
|
|
581
|
+
// icon button like any other (the SHC console's add and copy buttons).
|
|
582
|
+
const circular = circularProp && !(iconOnly && chromeless === true && plate == null);
|
|
583
|
+
// LC-176: inside a group's shared capsule a button draws its glyph alone.
|
|
584
|
+
const groupGround = useIconButtonGroupGround();
|
|
585
|
+
const mediaPlate = iconOnly && plate === "media" && groupGround !== "media";
|
|
586
|
+
const confirmPlate = iconOnly && primaryPlate;
|
|
587
|
+
const iconButton =
|
|
588
|
+
iconOnly &&
|
|
589
|
+
!mediaPlate &&
|
|
590
|
+
!confirmPlate &&
|
|
591
|
+
!grouped &&
|
|
592
|
+
chromeless !== false &&
|
|
593
|
+
outlined !== true &&
|
|
594
|
+
!circular;
|
|
489
595
|
// LC-153 CONTROL-TIERS: a bar paints its neutral buttons plain; content
|
|
490
596
|
// paints them tinted. The scheme picks the wash step.
|
|
491
597
|
// A Button is most overlays' trigger: the modality must be known before its
|
|
492
598
|
// opening key press, or the open focus cannot tell keyboard from pointer.
|
|
493
599
|
if (isWeb) ensureKeyboardModalityTracking();
|
|
494
600
|
const barSurface = useBarSurface();
|
|
601
|
+
// LC-176: the context picks the icon button's treatment, never the app. In a
|
|
602
|
+
// bar it takes the bar tint (the accent, on Liquid Glass where iOS draws
|
|
603
|
+
// it); in content, with no intent, it is a bold glyph in the text colour.
|
|
604
|
+
// An action inline with a value (copy beside an address) is tinted like a
|
|
605
|
+
// link, so it reads as the one pressable thing on the line.
|
|
606
|
+
const neutralGlyph =
|
|
607
|
+
iconButton && !(theme ?? intent) && !barSurface && accentInk == null && inline !== true;
|
|
608
|
+
if (iconButton) {
|
|
609
|
+
chromeless = true;
|
|
610
|
+
accentInk = accentInk ?? (!(theme ?? intent) && (barSurface || inline === true));
|
|
611
|
+
}
|
|
612
|
+
// LC-176: a glyph that is the whole button is bold, so it reads as one.
|
|
613
|
+
const resolvedGlyphWeight = glyphWeight ?? (iconOnly ? "bold" : "regular");
|
|
495
614
|
const elevatedGround = useElevatedSurface();
|
|
496
615
|
const darkScheme = isDarkThemeName(useThemeName());
|
|
497
616
|
const resolvedSize = typeof sizeProp === "number" ? sizeProp : knobProps.sizeToken;
|
|
@@ -590,25 +709,34 @@ export function Button({
|
|
|
590
709
|
const washOnHost = Boolean(
|
|
591
710
|
unavailable && activeThemeName && solidPaletteThemes.has(activeThemeName),
|
|
592
711
|
);
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
:
|
|
600
|
-
?
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
712
|
+
// LC-40: a chromeless button has no chrome to wash. A fill would make the
|
|
713
|
+
// disabled twin (a pager's first-page chevron) read as more prominent than
|
|
714
|
+
// the enabled one, so it keeps its frameless look and its ink dims.
|
|
715
|
+
const frameless = Boolean(chromeless);
|
|
716
|
+
const disabledSurface = frameless
|
|
717
|
+
? disabledState.glyphKnobProps
|
|
718
|
+
: tierDisabled
|
|
719
|
+
? {
|
|
720
|
+
...disabledState.surfaceKnobProps,
|
|
721
|
+
backgroundColor: tierDisabled.backgroundColor,
|
|
722
|
+
borderColor: tierDisabled.borderColor,
|
|
723
|
+
}
|
|
724
|
+
: washOnHost
|
|
725
|
+
? hostScoped(disabledState.surfaceKnobProps)
|
|
726
|
+
: disabledState.surfaceKnobProps;
|
|
727
|
+
const disabledText: { color?: string } = frameless
|
|
728
|
+
? {}
|
|
729
|
+
: tierDisabled
|
|
730
|
+
? { ...disabledState.textKnobProps, color: tierDisabled.color }
|
|
731
|
+
: washOnHost
|
|
732
|
+
? hostScoped(disabledState.textKnobProps)
|
|
733
|
+
: activeThemeName &&
|
|
734
|
+
solidButtonIntents.has(activeThemeName) &&
|
|
735
|
+
disabledState.textKnobProps.color
|
|
736
|
+
? { ...disabledState.textKnobProps, color: "$color12" }
|
|
737
|
+
: disabledState.textKnobProps;
|
|
738
|
+
outlined =
|
|
739
|
+
tier || iconButton || mediaPlate || confirmPlate ? false : (outlined ?? knobProps.outlined);
|
|
612
740
|
const isLow = knobProps.textAccent === "low";
|
|
613
741
|
const isMedium = knobProps.textAccent === "medium";
|
|
614
742
|
const resolvedBorderWidth = knobProps.borderRadius.borderWidth;
|
|
@@ -641,18 +769,22 @@ export function Button({
|
|
|
641
769
|
// (measured 1.02–1.06:1 on the page in both schemes). At the default
|
|
642
770
|
// textAccent (`high`, textColor undefined) the label must re-anchor to the
|
|
643
771
|
// same readable text tier the frame uses.
|
|
772
|
+
// LC-176: an intent icon button is a glyph on the page too, so it takes
|
|
773
|
+
// the outlined button's on-page ink.
|
|
774
|
+
const onPage = outlined || iconButton;
|
|
644
775
|
const outlinedIntentInk = Boolean(
|
|
645
|
-
|
|
776
|
+
onPage && activeThemeName && solidButtonIntents.has(activeThemeName),
|
|
646
777
|
);
|
|
647
778
|
// MPO-447: a solid-palette theme (accent) has no readable-on-page step at
|
|
648
779
|
// all, `$color11` included, so an outlined one takes its ink from the host:
|
|
649
780
|
// the accent that clears AA there, or host-scoped text under a textAccent.
|
|
650
781
|
const outlinedOnHost = Boolean(
|
|
651
|
-
|
|
782
|
+
onPage && activeThemeName && solidPaletteThemes.has(activeThemeName),
|
|
652
783
|
);
|
|
653
784
|
const outlinedInk = outlinedOnHost ? hostAccentInk : outlinedIntentInk ? "$color11" : undefined;
|
|
654
785
|
|
|
655
|
-
const resolvedRole: ButtonActionRole | undefined =
|
|
786
|
+
const resolvedRole: ButtonActionRole | undefined =
|
|
787
|
+
actionRole ?? (accent || confirmPlate ? "primary" : undefined);
|
|
656
788
|
const labelFromParts =
|
|
657
789
|
children == null && verb != null ? formatActionLabel({ verb, noun }) : null;
|
|
658
790
|
const resolvedChildren = children ?? labelFromParts;
|
|
@@ -661,17 +793,53 @@ export function Button({
|
|
|
661
793
|
];
|
|
662
794
|
const isSelected =
|
|
663
795
|
selected ?? (ariaPressedProp === true || ariaPressedProp === "true" ? true : undefined);
|
|
664
|
-
|
|
796
|
+
// The media plate is its own state ground; a toggle over media shows its
|
|
797
|
+
// state in the glyph.
|
|
798
|
+
const selectedFill = Boolean(isSelected) && !unavailable && !mediaPlate;
|
|
665
799
|
const barInk = accentInk && chromeless && !selectedFill && !unavailable ? hostIdentityInk : null;
|
|
800
|
+
const inlineGlyph = iconButton && inline === true && !nested;
|
|
801
|
+
// LC-176: back's caret beside its label is a bar glyph too, and so is the
|
|
802
|
+
// glyph on a plate (media, or the overlay's confirm).
|
|
803
|
+
const barGlyph =
|
|
804
|
+
((iconButton || mediaPlate || confirmPlate) && !nested) ||
|
|
805
|
+
(labelled && chromeless && accentInk === true && edge === "start");
|
|
806
|
+
const labelLine = labelLinePx(knobProps.body.fontFamily);
|
|
807
|
+
const glyphFloor = iconButtonGlyphPx(labelLine);
|
|
808
|
+
// LC-176: on iOS 26 a bar's icon button floats on Liquid Glass, the native
|
|
809
|
+
// bar button. Elsewhere, and on a binary without the module, it stays the
|
|
810
|
+
// bare tinted glyph.
|
|
811
|
+
const glassGroup = groupGround === "glass";
|
|
812
|
+
const GlassView =
|
|
813
|
+
iconButton && !inlineGlyph && !nested && accentInk && barSurface && !selectedFill && !glassGroup
|
|
814
|
+
? loadGlassView()
|
|
815
|
+
: null;
|
|
816
|
+
const framedCircle = iconOnly && Boolean(circular) && !nested && !mediaPlate && !confirmPlate;
|
|
817
|
+
const framedCirclePx =
|
|
818
|
+
touch && typeof sizeProp !== "number"
|
|
819
|
+
? Math.max(sizeTokenPx(resolvedSize), MIN_PRESS_TARGET)
|
|
820
|
+
: sizeTokenPx(resolvedSize);
|
|
666
821
|
const iconSize = nested
|
|
667
822
|
? Math.min(recipe.iconSize, Math.max(12, Math.round(knobProps.nestedControl.px * 0.45)))
|
|
668
|
-
:
|
|
823
|
+
: framedCircle
|
|
824
|
+
? Math.round(framedCirclePx * ICON_BUTTON_CIRCLE_GLYPH_RATIO)
|
|
825
|
+
: inlineGlyph
|
|
826
|
+
? Math.round(recipe.fontSize * INLINE_GLYPH_RATIO)
|
|
827
|
+
: barGlyph
|
|
828
|
+
? touch
|
|
829
|
+
? Math.max(glyphFloor, resolveButtonSizeRecipe("$6", recipeFamily, touch).iconSize)
|
|
830
|
+
: // A pointer toolbar keeps its token box (LC-54); the glyph fills it
|
|
831
|
+
// up to the same floor.
|
|
832
|
+
Math.max(recipe.iconSize, Math.min(glyphFloor, recipe.height - 8))
|
|
833
|
+
: recipe.iconSize;
|
|
834
|
+
const glyph =
|
|
835
|
+
barGlyph || inlineGlyph || framedCircle
|
|
836
|
+
? withGlyphWeight(icon, iconSize, resolvedGlyphWeight)
|
|
837
|
+
: icon;
|
|
669
838
|
|
|
670
839
|
// LC-158: the frame keeps its recipe height under Dynamic Type, so the
|
|
671
840
|
// label grows only as far as that height holds: its em and its line, which
|
|
672
841
|
// React Native scales by the same multiplier.
|
|
673
842
|
const labelBoxPx = nested ? knobProps.nestedControl.px : recipe.height;
|
|
674
|
-
const labelLine = labelLinePx(knobProps.body.fontFamily);
|
|
675
843
|
const labelMaxFontScale = Math.min(
|
|
676
844
|
maxFontScaleToFit(
|
|
677
845
|
labelBoxPx,
|
|
@@ -740,21 +908,25 @@ export function Button({
|
|
|
740
908
|
<View flexShrink={0} data-button-loading="" aria-hidden>
|
|
741
909
|
<Spinner px={iconSize} color={nativeIconColor as string} aria-hidden />
|
|
742
910
|
</View>
|
|
743
|
-
) :
|
|
911
|
+
) : glyph ? (
|
|
744
912
|
<View flexShrink={0}>
|
|
745
913
|
{isWeb ? (
|
|
746
|
-
normalizeButtonIcon(
|
|
914
|
+
normalizeButtonIcon(glyph, iconSize)
|
|
747
915
|
) : (
|
|
748
|
-
<NativeButtonIcon icon={
|
|
916
|
+
<NativeButtonIcon icon={glyph} size={iconSize} color={nativeIconColor} />
|
|
749
917
|
)}
|
|
750
918
|
</View>
|
|
751
919
|
) : null;
|
|
752
|
-
const
|
|
920
|
+
const glyphAfter =
|
|
921
|
+
iconAfter && (barGlyph || inlineGlyph || framedCircle)
|
|
922
|
+
? withGlyphWeight(iconAfter, iconSize, resolvedGlyphWeight)
|
|
923
|
+
: iconAfter;
|
|
924
|
+
const iconAfterElement = glyphAfter ? (
|
|
753
925
|
<View flexShrink={0}>
|
|
754
926
|
{isWeb ? (
|
|
755
|
-
normalizeButtonIcon(
|
|
927
|
+
normalizeButtonIcon(glyphAfter, iconSize)
|
|
756
928
|
) : (
|
|
757
|
-
<NativeButtonIcon icon={
|
|
929
|
+
<NativeButtonIcon icon={glyphAfter} size={iconSize} color={nativeIconColor} />
|
|
758
930
|
)}
|
|
759
931
|
</View>
|
|
760
932
|
) : null;
|
|
@@ -766,7 +938,13 @@ export function Button({
|
|
|
766
938
|
: undefined;
|
|
767
939
|
const isCircular = Boolean(circular);
|
|
768
940
|
const circularTokenPx = sizeTokenPx(resolvedSize);
|
|
769
|
-
const visualPx = nested
|
|
941
|
+
const visualPx = nested
|
|
942
|
+
? knobProps.nestedControl.px
|
|
943
|
+
: inlineGlyph
|
|
944
|
+
? iconSize
|
|
945
|
+
: iconButton
|
|
946
|
+
? Math.max(recipe.height, iconSize)
|
|
947
|
+
: recipe.height;
|
|
770
948
|
// Touch circular tokens below 44 need an explicit box floor (circular variant also
|
|
771
949
|
// sets maxWidth/maxHeight to the token). hitSlop covers native/touch leftovers.
|
|
772
950
|
// Nested circulars skip this — they paint nestedControl and restore press
|
|
@@ -836,6 +1014,27 @@ export function Button({
|
|
|
836
1014
|
const restFill = selectedFill ? (outlined ? "$background" : "$backgroundPress") : tierPaint?.rest;
|
|
837
1015
|
// A plain button is ink on the bar: no shadow at rest (E-FLAT).
|
|
838
1016
|
const plainTier = tier === "plain";
|
|
1017
|
+
// LC-176: hover and press light a soft plate on web; on native the glyph
|
|
1018
|
+
// dims, as an iOS bar button does. The media plate deepens instead.
|
|
1019
|
+
const glyphState = iconButton && !selectedFill;
|
|
1020
|
+
const iconHover = glyphState
|
|
1021
|
+
? isWeb
|
|
1022
|
+
? hostScoped({ backgroundColor: "$backgroundHover" })
|
|
1023
|
+
: { backgroundColor: "transparent" }
|
|
1024
|
+
: mediaPlate
|
|
1025
|
+
? { backgroundColor: "$shadow5" }
|
|
1026
|
+
: undefined;
|
|
1027
|
+
const iconPress = glyphState
|
|
1028
|
+
? isWeb
|
|
1029
|
+
? hostScoped({ backgroundColor: "$backgroundPress" })
|
|
1030
|
+
: { backgroundColor: "transparent", opacity: ICON_BUTTON_PRESS_OPACITY }
|
|
1031
|
+
: mediaPlate
|
|
1032
|
+
? { backgroundColor: "$shadow5" }
|
|
1033
|
+
: undefined;
|
|
1034
|
+
// A square box, so an `edge` pull puts the glyph itself on the gutter.
|
|
1035
|
+
// On glass the circle, not the glyph, sits on the margin.
|
|
1036
|
+
const glyphEdgePull =
|
|
1037
|
+
GlassView || glassGroup ? 0 : iconButton ? -Math.max(0, (visualPx - iconSize) / 2) : edgePull;
|
|
839
1038
|
const buttonElement = (
|
|
840
1039
|
<ButtonFrame
|
|
841
1040
|
// DG-ST-03: an explained disable is aria-disabled (focusable, press
|
|
@@ -850,8 +1049,10 @@ export function Button({
|
|
|
850
1049
|
: undefined)}
|
|
851
1050
|
borderWidth={resolvedBorderWidth}
|
|
852
1051
|
size={resolvedSize}
|
|
853
|
-
{...(chromeless || plainTier
|
|
854
|
-
|
|
1052
|
+
{...(chromeless || plainTier || mediaPlate || confirmPlate
|
|
1053
|
+
? undefined
|
|
1054
|
+
: knobProps.elevationChrome)}
|
|
1055
|
+
elevation={plainTier || confirmPlate ? undefined : knobProps.elevation}
|
|
855
1056
|
// The frame's `outlined` variant declares only a `true` branch, so a
|
|
856
1057
|
// literal `false` is not consumed and React receives it as a DOM
|
|
857
1058
|
// attribute ("Received `false` for a non-boolean attribute"). Every
|
|
@@ -877,10 +1078,24 @@ export function Button({
|
|
|
877
1078
|
minHeight: visualPx,
|
|
878
1079
|
}
|
|
879
1080
|
: undefined)}
|
|
880
|
-
{...(
|
|
1081
|
+
{...(iconButton || mediaPlate || confirmPlate
|
|
1082
|
+
? { width: visualPx, minWidth: visualPx, paddingHorizontal: 0 }
|
|
1083
|
+
: undefined)}
|
|
1084
|
+
{...(inlineGlyph ? { marginInlineStart: "$2" as const } : undefined)}
|
|
1085
|
+
// The glass rounds itself, unclipped.
|
|
1086
|
+
{...(GlassView ? { borderRadius: 100000 } : undefined)}
|
|
1087
|
+
{...(confirmPlate ? { borderRadius: 100000 } : undefined)}
|
|
1088
|
+
{...(mediaPlate
|
|
1089
|
+
? {
|
|
1090
|
+
borderRadius: 100000,
|
|
1091
|
+
backgroundColor: "$shadow4",
|
|
1092
|
+
...(isWeb ? { style: MEDIA_PLATE_WEB_STYLE } : undefined),
|
|
1093
|
+
}
|
|
1094
|
+
: undefined)}
|
|
1095
|
+
{...(edge && !inlineGlyph && chromeless && !selectedFill && !isCircular
|
|
881
1096
|
? edge === "start"
|
|
882
|
-
? { marginInlineStart:
|
|
883
|
-
: { marginInlineEnd:
|
|
1097
|
+
? { marginInlineStart: glyphEdgePull }
|
|
1098
|
+
: { marginInlineEnd: glyphEdgePull }
|
|
884
1099
|
: undefined)}
|
|
885
1100
|
{...(restFill ? { backgroundColor: restFill } : undefined)}
|
|
886
1101
|
// Solid-intent sub-themes carry the on-fill foreground as `$color`
|
|
@@ -915,6 +1130,7 @@ export function Button({
|
|
|
915
1130
|
...text.hoverKnobProps,
|
|
916
1131
|
// A selected fill, or the LC-153 tier's wash, beats the knob state fill.
|
|
917
1132
|
...(selectedFill || tierPaint ? { backgroundColor: selectedHoverFill } : undefined),
|
|
1133
|
+
...iconHover,
|
|
918
1134
|
}}
|
|
919
1135
|
pressStyle={{
|
|
920
1136
|
backgroundColor: selectedPressFill,
|
|
@@ -923,6 +1139,7 @@ export function Button({
|
|
|
923
1139
|
...text.pressKnobProps,
|
|
924
1140
|
// A selected fill, or the LC-153 tier's wash, beats the knob state fill.
|
|
925
1141
|
...(selectedFill || tierPaint ? { backgroundColor: selectedPressFill } : undefined),
|
|
1142
|
+
...iconPress,
|
|
926
1143
|
}}
|
|
927
1144
|
focusStyle={{ outlineWidth: 0 }}
|
|
928
1145
|
// DG-A11Y-01 / LC-71: house keyboard ring (≥2px `$outlineColor`) on the
|
|
@@ -971,6 +1188,9 @@ export function Button({
|
|
|
971
1188
|
}
|
|
972
1189
|
: undefined)}
|
|
973
1190
|
{...(isCircular && !grouped ? radiusClassProps("R-IDENTITY", "circular Button") : undefined)}
|
|
1191
|
+
{...(!isCircular && (GlassView || confirmPlate || mediaPlate)
|
|
1192
|
+
? radiusClassProps("R-IDENTITY", "icon button plate")
|
|
1193
|
+
: undefined)}
|
|
974
1194
|
{...(explainedDisable
|
|
975
1195
|
? {
|
|
976
1196
|
// No `title`: hover-only reveals are banned on disabled controls
|
|
@@ -981,11 +1201,11 @@ export function Button({
|
|
|
981
1201
|
// Hover/press keep the LC-40 washed chrome — an aria-disabled
|
|
982
1202
|
// control must not light up like a pressable one.
|
|
983
1203
|
hoverStyle: {
|
|
984
|
-
backgroundColor: outlined ? "transparent" : "$background",
|
|
1204
|
+
backgroundColor: outlined || frameless ? "transparent" : "$background",
|
|
985
1205
|
...disabledSurface,
|
|
986
1206
|
},
|
|
987
1207
|
pressStyle: {
|
|
988
|
-
backgroundColor: outlined ? "transparent" : "$background",
|
|
1208
|
+
backgroundColor: outlined || frameless ? "transparent" : "$background",
|
|
989
1209
|
...disabledSurface,
|
|
990
1210
|
},
|
|
991
1211
|
"data-mp-disabled-explained": "true",
|
|
@@ -1001,6 +1221,19 @@ export function Button({
|
|
|
1001
1221
|
: undefined)}
|
|
1002
1222
|
{...(resolvedRole ? { "data-mp-action-role": resolvedRole } : undefined)}
|
|
1003
1223
|
{...(tier ? { "data-mp-tier": tier } : undefined)}
|
|
1224
|
+
{...(iconButton
|
|
1225
|
+
? {
|
|
1226
|
+
"data-mp-icon-button": GlassView
|
|
1227
|
+
? "glass"
|
|
1228
|
+
: inlineGlyph
|
|
1229
|
+
? "inline"
|
|
1230
|
+
: neutralGlyph
|
|
1231
|
+
? "neutral"
|
|
1232
|
+
: "glyph",
|
|
1233
|
+
}
|
|
1234
|
+
: undefined)}
|
|
1235
|
+
{...(mediaPlate ? { "data-mp-icon-button": "media" } : undefined)}
|
|
1236
|
+
{...(confirmPlate ? { "data-mp-icon-button": "primary" } : undefined)}
|
|
1004
1237
|
data-mp-button-height={String(
|
|
1005
1238
|
nested
|
|
1006
1239
|
? visualPx
|
|
@@ -1013,17 +1246,35 @@ export function Button({
|
|
|
1013
1246
|
data-size-token={knobProps.sizeToken}
|
|
1014
1247
|
{...(selectedFill ? { "aria-pressed": true, "data-mp-selected": "true" } : undefined)}
|
|
1015
1248
|
>
|
|
1249
|
+
{GlassView ? (
|
|
1250
|
+
<GlassView
|
|
1251
|
+
glassEffectStyle="regular"
|
|
1252
|
+
isInteractive
|
|
1253
|
+
pointerEvents="none"
|
|
1254
|
+
style={{
|
|
1255
|
+
position: "absolute",
|
|
1256
|
+
top: 0,
|
|
1257
|
+
right: 0,
|
|
1258
|
+
bottom: 0,
|
|
1259
|
+
left: 0,
|
|
1260
|
+
borderRadius: visualPx / 2,
|
|
1261
|
+
}}
|
|
1262
|
+
/>
|
|
1263
|
+
) : null}
|
|
1016
1264
|
{iconElement}
|
|
1017
1265
|
{labelContent}
|
|
1018
1266
|
{iconAfterElement}
|
|
1019
1267
|
</ButtonFrame>
|
|
1020
1268
|
);
|
|
1021
1269
|
|
|
1022
|
-
const
|
|
1270
|
+
const intentButton = activeThemeName ? (
|
|
1023
1271
|
<Theme name={activeThemeName as ThemeName}>{buttonElement}</Theme>
|
|
1024
1272
|
) : (
|
|
1025
1273
|
buttonElement
|
|
1026
1274
|
);
|
|
1275
|
+
// The media plate reads the same over any picture in either scheme: a dark
|
|
1276
|
+
// material with a light glyph.
|
|
1277
|
+
const themedButton = mediaPlate ? <Theme name="dark">{intentButton}</Theme> : intentButton;
|
|
1027
1278
|
|
|
1028
1279
|
// DG-ST-03 explain affordance: visible reason adjacent to the control —
|
|
1029
1280
|
// discoverable by pointer, touch, keyboard, and SR (aria-describedby)
|
|
@@ -1060,3 +1311,24 @@ export function Button({
|
|
|
1060
1311
|
|
|
1061
1312
|
Button.Icon = TamaguiButton.Icon;
|
|
1062
1313
|
Button.Text = TamaguiSizableText;
|
|
1314
|
+
|
|
1315
|
+
export interface IconButtonProps extends Omit<
|
|
1316
|
+
ButtonProps,
|
|
1317
|
+
"children" | "icon" | "verb" | "noun" | "outlined" | "circular"
|
|
1318
|
+
> {
|
|
1319
|
+
icon: NonNullable<ButtonProps["icon"]>;
|
|
1320
|
+
/** The action's name, read by assistive tech: the button shows only its glyph. */
|
|
1321
|
+
"aria-label": string;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* LC-176 ICON-BUTTON: a toolbar, nav-bar, row or field-accessory action drawn
|
|
1326
|
+
* as its glyph alone (HIG bar buttons, Material's standard icon button).
|
|
1327
|
+
* `plate="media"` floats it over imagery on a material circle.
|
|
1328
|
+
*/
|
|
1329
|
+
export function IconButton(props: IconButtonProps) {
|
|
1330
|
+
return <Button {...props} />;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
export { IconButtonGroup, ICON_BUTTON_GLASS_MERGE_PX } from "./IconButtonGroup";
|
|
1334
|
+
export type { IconButtonGroupProps } from "./IconButtonGroup";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-176: the media plate's material on web, where a blur behind the plate is
|
|
3
|
+
* the platform's material; native keeps the translucent ground alone. One
|
|
4
|
+
* copy, shared by a lone plate and a group's capsule.
|
|
5
|
+
*/
|
|
6
|
+
export const MEDIA_PLATE_WEB_STYLE = {
|
|
7
|
+
backdropFilter: "blur(16px) saturate(180%)",
|
|
8
|
+
WebkitBackdropFilter: "blur(16px) saturate(180%)",
|
|
9
|
+
} as const;
|
|
@@ -16,7 +16,9 @@ function savebar(root: ParentNode = document.body): HTMLElement | null {
|
|
|
16
16
|
|
|
17
17
|
function barButton(label: string, root: ParentNode = document.body): HTMLElement {
|
|
18
18
|
const buttons = Array.from(root.querySelectorAll('[role="button"], button'));
|
|
19
|
-
const match = buttons.find(
|
|
19
|
+
const match = buttons.find(
|
|
20
|
+
(b) => b.textContent?.includes(label) || b.getAttribute("aria-label") === label,
|
|
21
|
+
);
|
|
20
22
|
if (!match) throw new Error(`No button labelled "${label}"`);
|
|
21
23
|
return match as HTMLElement;
|
|
22
24
|
}
|
|
@@ -2,7 +2,6 @@ import { action } from "@multiplatform.one/storybook";
|
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { YStack } from "tamagui";
|
|
5
|
-
import { Button } from "../Button";
|
|
6
5
|
import { Checkbox } from "../fields/Checkbox";
|
|
7
6
|
import { Input } from "../fields/Input";
|
|
8
7
|
import { RadioGroup } from "../fields/RadioGroup";
|
|
@@ -10,6 +9,7 @@ import { Switch } from "../fields/Switch";
|
|
|
10
9
|
import { Form } from "../Form";
|
|
11
10
|
import { ContextualSaveBar } from "./index";
|
|
12
11
|
import { SizableText } from "@multiplatform.one/theme";
|
|
12
|
+
import { ActionButton } from "@multiplatform.one/components";
|
|
13
13
|
|
|
14
14
|
const fields = (
|
|
15
15
|
<YStack gap="$4" width="100%" maxWidth={640} padding="$4" minHeight={360}>
|
|
@@ -127,7 +127,7 @@ function SubmissionPathsFixture() {
|
|
|
127
127
|
}}
|
|
128
128
|
>
|
|
129
129
|
<Input name="name" label="Service name" />
|
|
130
|
-
<
|
|
130
|
+
<ActionButton kind="submit" formAction="submit" />
|
|
131
131
|
</Form>
|
|
132
132
|
</YStack>
|
|
133
133
|
);
|
|
@@ -3,7 +3,8 @@ import { useResolvedKnobs, warnCancelDisabled, type KnobProps } from "@multiplat
|
|
|
3
3
|
import { Store, useStore } from "@tanstack/react-store";
|
|
4
4
|
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
|
5
5
|
import { AnimatePresence, Text, XStack, isWeb } from "tamagui";
|
|
6
|
-
import { Button } from "../Button";
|
|
6
|
+
import { Button, IconButton } from "../Button";
|
|
7
|
+
import { standardActions } from "../standardActions";
|
|
7
8
|
import { collectFormFieldErrors } from "../ErrorSummary";
|
|
8
9
|
import { useFormContext } from "../Form";
|
|
9
10
|
import { getFormSubmission } from "../Form/submissionController";
|
|
@@ -321,21 +322,17 @@ export function ContextualSaveBar({
|
|
|
321
322
|
>
|
|
322
323
|
{t("Discard")}
|
|
323
324
|
</Button>
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
size="$4"
|
|
329
|
-
icon={snap.isSubmitting ? <Spinner size="small" /> : undefined}
|
|
325
|
+
<IconButton
|
|
326
|
+
plate="primary"
|
|
327
|
+
icon={snap.isSubmitting ? <Spinner size="small" /> : standardActions.save.icon}
|
|
328
|
+
aria-label={t("Save")}
|
|
330
329
|
disabled={snap.isSubmitting || undefined}
|
|
331
330
|
disabledReason={snap.isSubmitting ? t("Saving") : undefined}
|
|
332
331
|
data-mp-savebar-save=""
|
|
333
332
|
onPress={() => {
|
|
334
333
|
void handleSave();
|
|
335
334
|
}}
|
|
336
|
-
|
|
337
|
-
{t("Save")}
|
|
338
|
-
</Button>
|
|
335
|
+
/>
|
|
339
336
|
</XStack>
|
|
340
337
|
</XStack>
|
|
341
338
|
) : null}
|
|
@@ -3,6 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
|
3
3
|
import { YStack } from "tamagui";
|
|
4
4
|
import { Button } from "../Button";
|
|
5
5
|
import { Input } from "../InputParts";
|
|
6
|
+
import { ActionButton } from "@multiplatform.one/components";
|
|
6
7
|
import { ControlGroup } from "./index";
|
|
7
8
|
import { Paragraph } from "@multiplatform.one/theme";
|
|
8
9
|
|
|
@@ -130,11 +131,11 @@ export const Connected: Story = {
|
|
|
130
131
|
<Button>Go</Button>
|
|
131
132
|
</ControlGroup>
|
|
132
133
|
<ControlGroup>
|
|
133
|
-
<
|
|
134
|
+
<ActionButton kind="search" />
|
|
134
135
|
<Input.Box>
|
|
135
136
|
<Input.Area placeholder="query" flex={1} />
|
|
136
137
|
</Input.Box>
|
|
137
|
-
<
|
|
138
|
+
<ActionButton kind="filter" />
|
|
138
139
|
</ControlGroup>
|
|
139
140
|
</YStack>
|
|
140
141
|
),
|
|
@@ -3,6 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
|
3
3
|
import { useForm } from "@tanstack/react-form";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import { YStack } from "tamagui";
|
|
6
|
+
import { ActionButton } from "@multiplatform.one/components";
|
|
6
7
|
import { Button } from "./Button";
|
|
7
8
|
import { ErrorSummary, collectFormFieldErrors } from "./ErrorSummary";
|
|
8
9
|
import { FieldLayout } from "./fieldLayout";
|
|
@@ -168,7 +169,7 @@ export const FocusesTheFieldOnPress: Story = {
|
|
|
168
169
|
</InputParts>
|
|
169
170
|
</FieldLayout>
|
|
170
171
|
</FormSection>
|
|
171
|
-
<
|
|
172
|
+
<ActionButton kind="submit" onPress={submit} />
|
|
172
173
|
</YStack>
|
|
173
174
|
</Form>
|
|
174
175
|
);
|