jfs-components 0.1.2 → 0.1.12
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/CHANGELOG.md +45 -0
- package/lib/commonjs/components/Accordion/Accordion.js +2 -1
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +4 -3
- package/lib/commonjs/components/AccountCard/AccountCard.js +4 -3
- package/lib/commonjs/components/AllocationComparisonChart/AllocationComparisonChart.js +9 -6
- package/lib/commonjs/components/AmountInput/AmountInput.js +8 -5
- package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +11 -7
- package/lib/commonjs/components/Badge/Badge.js +47 -8
- package/lib/commonjs/components/BenefitCard/BenefitCard.js +231 -0
- package/lib/commonjs/components/BrandChip/BrandChip.js +3 -3
- package/lib/commonjs/components/Button/Button.js +2 -1
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -1
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +3 -2
- package/lib/commonjs/components/CardCTA/CardCTA.js +197 -27
- package/lib/commonjs/components/CardInsight/CardInsight.js +5 -1
- package/lib/commonjs/components/CcCard/CcCard.js +470 -0
- package/lib/commonjs/components/Checkbox/Checkbox.js +4 -3
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +4 -3
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +7 -3
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +3 -2
- package/lib/commonjs/components/CircularRating/CircularRating.js +2 -1
- package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +3 -2
- package/lib/commonjs/components/CompareTable/CompareTable.js +401 -0
- package/lib/commonjs/components/ComparisonBar/ComparisonBar.js +266 -0
- package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +3 -2
- package/lib/commonjs/components/DonutChart/DonutChart.js +4 -3
- package/lib/commonjs/components/Dropdown/Dropdown.js +3 -2
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +38 -5
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
- package/lib/commonjs/components/FormField/FormField.js +45 -4
- package/lib/commonjs/components/Gauge/Gauge.js +1 -1
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +3 -2
- package/lib/commonjs/components/InputSearch/InputSearch.js +6 -4
- package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +2 -1
- package/lib/commonjs/components/ListGroup/ListGroup.js +2 -1
- package/lib/commonjs/components/ListItem/ListItem.js +3 -2
- package/lib/commonjs/components/MediaCard/MediaCard.js +4 -2
- package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +3 -2
- package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +6 -3
- package/lib/commonjs/components/NoteInput/NoteInput.js +6 -5
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +273 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +3 -2
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +427 -0
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +4 -3
- package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -2
- package/lib/commonjs/components/ProjectionMarker/ProjectionMarker.js +162 -0
- package/lib/commonjs/components/Radio/Radio.js +5 -5
- package/lib/commonjs/components/Section/Section.js +3 -2
- package/lib/commonjs/components/Slider/Slider.js +474 -0
- package/lib/commonjs/components/Stack/Stack.js +86 -0
- package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
- package/lib/commonjs/components/SummaryTile/SummaryTile.js +3 -2
- package/lib/commonjs/components/Table/Table.js +324 -0
- package/lib/commonjs/components/Text/Text.js +2 -1
- package/lib/commonjs/components/TextInput/TextInput.js +22 -9
- package/lib/commonjs/components/TextSegment/TextSegment.js +119 -0
- package/lib/commonjs/components/ThreadHero/ThreadHero.js +4 -5
- package/lib/commonjs/components/Title/Title.js +2 -1
- package/lib/commonjs/components/Toast/Toast.js +3 -2
- package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +2 -1
- package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +2 -2
- package/lib/commonjs/components/index.js +107 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +39 -9
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/GlassFill/GlassFill.js +271 -0
- package/lib/commonjs/utils/GlassFill/GlassFill.web.js +120 -0
- package/lib/commonjs/utils/react-utils.js +78 -0
- package/lib/commonjs/utils/web-platform-utils.js +59 -0
- package/lib/module/components/Accordion/Accordion.js +3 -2
- package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +5 -4
- package/lib/module/components/AccountCard/AccountCard.js +5 -4
- package/lib/module/components/AllocationComparisonChart/AllocationComparisonChart.js +10 -7
- package/lib/module/components/AmountInput/AmountInput.js +6 -4
- package/lib/module/components/AreaLineChart/AreaLineChart.js +12 -8
- package/lib/module/components/Badge/Badge.js +49 -10
- package/lib/module/components/BenefitCard/BenefitCard.js +225 -0
- package/lib/module/components/BrandChip/BrandChip.js +4 -4
- package/lib/module/components/Button/Button.js +3 -2
- package/lib/module/components/CardAdvisory/CardAdvisory.js +3 -2
- package/lib/module/components/CardBankAccount/CardBankAccount.js +4 -3
- package/lib/module/components/CardCTA/CardCTA.js +198 -28
- package/lib/module/components/CardInsight/CardInsight.js +5 -1
- package/lib/module/components/CcCard/CcCard.js +464 -0
- package/lib/module/components/Checkbox/Checkbox.js +5 -4
- package/lib/module/components/CheckboxItem/CheckboxItem.js +5 -4
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +8 -4
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +4 -3
- package/lib/module/components/CircularRating/CircularRating.js +3 -2
- package/lib/module/components/ClusterBubble/ClusterBubble.js +4 -3
- package/lib/module/components/CompareTable/CompareTable.js +396 -0
- package/lib/module/components/ComparisonBar/ComparisonBar.js +260 -0
- package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +4 -3
- package/lib/module/components/DonutChart/DonutChart.js +5 -4
- package/lib/module/components/Dropdown/Dropdown.js +4 -3
- package/lib/module/components/DropdownInput/DropdownInput.js +40 -7
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +2 -1
- package/lib/module/components/FormField/FormField.js +48 -7
- package/lib/module/components/Gauge/Gauge.js +1 -1
- package/lib/module/components/HoldingsCard/HoldingsCard.js +4 -3
- package/lib/module/components/InputSearch/InputSearch.js +6 -4
- package/lib/module/components/InstitutionBadge/InstitutionBadge.js +3 -2
- package/lib/module/components/ListGroup/ListGroup.js +3 -2
- package/lib/module/components/ListItem/ListItem.js +4 -3
- package/lib/module/components/MediaCard/MediaCard.js +4 -2
- package/lib/module/components/MetricLegendItem/MetricLegendItem.js +4 -3
- package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +7 -4
- package/lib/module/components/NoteInput/NoteInput.js +7 -6
- package/lib/module/components/PdpCcCard/PdpCcCard.js +267 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +4 -3
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +421 -0
- package/lib/module/components/ProductOverview/ProductOverview.js +5 -4
- package/lib/module/components/ProgressBadge/ProgressBadge.js +3 -3
- package/lib/module/components/ProjectionMarker/ProjectionMarker.js +157 -0
- package/lib/module/components/Radio/Radio.js +5 -4
- package/lib/module/components/Section/Section.js +4 -3
- package/lib/module/components/Slider/Slider.js +469 -0
- package/lib/module/components/Stack/Stack.js +81 -0
- package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +10 -1
- package/lib/module/components/SummaryTile/SummaryTile.js +4 -3
- package/lib/module/components/Table/Table.js +315 -0
- package/lib/module/components/Text/Text.js +3 -2
- package/lib/module/components/TextInput/TextInput.js +24 -11
- package/lib/module/components/TextSegment/TextSegment.js +114 -0
- package/lib/module/components/ThreadHero/ThreadHero.js +5 -6
- package/lib/module/components/Title/Title.js +3 -2
- package/lib/module/components/Toast/Toast.js +4 -3
- package/lib/module/components/TransactionBubble/TransactionBubble.js +3 -2
- package/lib/module/components/TransactionStatus/TransactionStatus.js +4 -3
- package/lib/module/components/UpiHandle/UpiHandle.js +3 -3
- package/lib/module/components/index.js +11 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +39 -9
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/GlassFill/GlassFill.js +265 -0
- package/lib/module/utils/GlassFill/GlassFill.web.js +115 -0
- package/lib/module/utils/react-utils.js +75 -0
- package/lib/module/utils/web-platform-utils.js +58 -0
- package/lib/typescript/src/components/Accordion/Accordion.d.ts +7 -1
- package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +7 -1
- package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +7 -1
- package/lib/typescript/src/components/AllocationComparisonChart/AllocationComparisonChart.d.ts +7 -1
- package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +3 -2
- package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +8 -1
- package/lib/typescript/src/components/Badge/Badge.d.ts +32 -2
- package/lib/typescript/src/components/BenefitCard/BenefitCard.d.ts +93 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +7 -1
- package/lib/typescript/src/components/Button/Button.d.ts +7 -1
- package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +7 -1
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +7 -1
- package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +27 -5
- package/lib/typescript/src/components/CcCard/CcCard.d.ts +137 -0
- package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +2 -2
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +7 -1
- package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +7 -1
- package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +7 -1
- package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +7 -1
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +94 -0
- package/lib/typescript/src/components/ComparisonBar/ComparisonBar.d.ts +118 -0
- package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +7 -1
- package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +7 -1
- package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +7 -1
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +26 -1
- package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +7 -1
- package/lib/typescript/src/components/FormField/FormField.d.ts +2 -2
- package/lib/typescript/src/components/HoldingsCard/HoldingsCard.d.ts +7 -1
- package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +23 -2
- package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +7 -1
- package/lib/typescript/src/components/ListGroup/ListGroup.d.ts +7 -1
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +7 -1
- package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
- package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +14 -2
- package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +19 -2
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +84 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +7 -1
- package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +87 -0
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +7 -1
- package/lib/typescript/src/components/ProgressBadge/ProgressBadge.d.ts +7 -1
- package/lib/typescript/src/components/ProjectionMarker/ProjectionMarker.d.ts +88 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +3 -2
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +2 -2
- package/lib/typescript/src/components/Section/Section.d.ts +7 -1
- package/lib/typescript/src/components/Slider/Slider.d.ts +105 -0
- package/lib/typescript/src/components/Stack/Stack.d.ts +81 -0
- package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +7 -1
- package/lib/typescript/src/components/Table/Table.d.ts +139 -0
- package/lib/typescript/src/components/Text/Text.d.ts +7 -1
- package/lib/typescript/src/components/TextInput/TextInput.d.ts +9 -29
- package/lib/typescript/src/components/TextSegment/TextSegment.d.ts +106 -0
- package/lib/typescript/src/components/ThreadHero/ThreadHero.d.ts +7 -1
- package/lib/typescript/src/components/Title/Title.d.ts +7 -1
- package/lib/typescript/src/components/Toast/Toast.d.ts +7 -1
- package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +7 -1
- package/lib/typescript/src/components/TransactionStatus/TransactionStatus.d.ts +7 -1
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
- package/lib/typescript/src/components/index.d.ts +14 -3
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +22 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/GlassFill/GlassFill.d.ts +84 -0
- package/lib/typescript/src/utils/GlassFill/GlassFill.web.d.ts +43 -0
- package/lib/typescript/src/utils/react-utils.d.ts +47 -0
- package/lib/typescript/src/utils/web-platform-utils.d.ts +36 -0
- package/package.json +2 -1
- package/src/components/Accordion/Accordion.tsx +9 -2
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +10 -3
- package/src/components/AccountCard/AccountCard.tsx +10 -3
- package/src/components/AllocationComparisonChart/AllocationComparisonChart.tsx +14 -4
- package/src/components/AmountInput/AmountInput.tsx +7 -5
- package/src/components/AreaLineChart/AreaLineChart.tsx +15 -5
- package/src/components/Badge/Badge.tsx +78 -8
- package/src/components/BenefitCard/BenefitCard.tsx +309 -0
- package/src/components/BrandChip/BrandChip.tsx +9 -3
- package/src/components/Button/Button.tsx +9 -2
- package/src/components/CardAdvisory/CardAdvisory.tsx +9 -2
- package/src/components/CardBankAccount/CardBankAccount.tsx +9 -2
- package/src/components/CardCTA/CardCTA.tsx +214 -41
- package/src/components/CardInsight/CardInsight.tsx +7 -1
- package/src/components/CcCard/CcCard.tsx +598 -0
- package/src/components/Checkbox/Checkbox.tsx +5 -4
- package/src/components/CheckboxItem/CheckboxItem.tsx +5 -4
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +14 -4
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +10 -3
- package/src/components/CircularRating/CircularRating.tsx +9 -2
- package/src/components/ClusterBubble/ClusterBubble.tsx +9 -2
- package/src/components/CompareTable/CompareTable.tsx +525 -0
- package/src/components/ComparisonBar/ComparisonBar.tsx +356 -0
- package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +9 -2
- package/src/components/DonutChart/DonutChart.tsx +10 -3
- package/src/components/Dropdown/Dropdown.tsx +9 -2
- package/src/components/DropdownInput/DropdownInput.tsx +64 -5
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +8 -1
- package/src/components/FormField/FormField.tsx +52 -6
- package/src/components/Gauge/Gauge.tsx +1 -1
- package/src/components/HoldingsCard/HoldingsCard.tsx +9 -2
- package/src/components/InputSearch/InputSearch.tsx +8 -5
- package/src/components/InstitutionBadge/InstitutionBadge.tsx +9 -2
- package/src/components/ListGroup/ListGroup.tsx +9 -2
- package/src/components/ListItem/ListItem.tsx +10 -3
- package/src/components/MediaCard/MediaCard.tsx +2 -2
- package/src/components/MetricLegendItem/MetricLegendItem.tsx +9 -2
- package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +17 -2
- package/src/components/NoteInput/NoteInput.tsx +8 -6
- package/src/components/PdpCcCard/PdpCcCard.tsx +356 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +9 -2
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +490 -0
- package/src/components/ProductOverview/ProductOverview.tsx +10 -3
- package/src/components/ProgressBadge/ProgressBadge.tsx +9 -3
- package/src/components/ProjectionMarker/ProjectionMarker.tsx +284 -0
- package/src/components/Radio/Radio.tsx +5 -4
- package/src/components/Section/Section.tsx +10 -3
- package/src/components/Slider/Slider.tsx +635 -0
- package/src/components/Stack/Stack.tsx +131 -0
- package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +10 -1
- package/src/components/SummaryTile/SummaryTile.tsx +9 -2
- package/src/components/Table/Table.tsx +449 -0
- package/src/components/Text/Text.tsx +9 -2
- package/src/components/TextInput/TextInput.tsx +22 -12
- package/src/components/TextSegment/TextSegment.tsx +173 -0
- package/src/components/ThreadHero/ThreadHero.tsx +10 -3
- package/src/components/Title/Title.tsx +9 -2
- package/src/components/Toast/Toast.tsx +9 -2
- package/src/components/TransactionBubble/TransactionBubble.tsx +9 -2
- package/src/components/TransactionStatus/TransactionStatus.tsx +9 -2
- package/src/components/UpiHandle/UpiHandle.tsx +9 -3
- package/src/components/index.ts +33 -3
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +39 -9
- package/src/icons/registry.ts +1 -1
- package/src/utils/GlassFill/GlassFill.tsx +305 -0
- package/src/utils/GlassFill/GlassFill.web.tsx +148 -0
- package/src/utils/react-utils.ts +84 -0
- package/src/utils/web-platform-utils.ts +68 -0
- package/lib/commonjs/components/MediaCard/GlassFill.js +0 -62
- package/lib/commonjs/components/MediaCard/GlassFill.web.js +0 -48
- package/lib/module/components/MediaCard/GlassFill.js +0 -57
- package/lib/module/components/MediaCard/GlassFill.web.js +0 -43
- package/lib/typescript/scripts/extract-component-tokens.d.ts +0 -9
- package/lib/typescript/scripts/generate-component-docs.d.ts +0 -9
- package/lib/typescript/scripts/generate-icon-registry.d.ts +0 -3
- package/lib/typescript/scripts/generate-mode-types.d.ts +0 -2
- package/lib/typescript/scripts/retype-modes.d.cts +0 -2
- package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +0 -47
- package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +0 -20
- package/src/components/MediaCard/GlassFill.tsx +0 -89
- package/src/components/MediaCard/GlassFill.web.tsx +0 -53
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
View,
|
|
5
|
+
type ImageSourcePropType,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
11
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
12
|
+
import { usePressableWebSupport } from '../../utils/web-platform-utils'
|
|
13
|
+
import Button from '../Button/Button'
|
|
14
|
+
import Image from '../Image/Image'
|
|
15
|
+
import IconCapsule from '../IconCapsule/IconCapsule'
|
|
16
|
+
import type { Modes } from '../../design-tokens'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A single slot in the {@link ComparisonBar}. Each item is either empty (the
|
|
20
|
+
* "Add" state — a tappable `+` capsule) or filled with an image (the
|
|
21
|
+
* "Image Added" state — the image plus a dismiss capsule in the corner).
|
|
22
|
+
*
|
|
23
|
+
* The presence of {@link ComparisonBarItem.imageSource} is what toggles the
|
|
24
|
+
* state: provide a source to show the image, leave it `undefined`/`null` to
|
|
25
|
+
* show the empty add slot. This keeps the component fully controlled — the
|
|
26
|
+
* `ComparisonBar` never owns the image state itself, so the consumer decides
|
|
27
|
+
* (e.g. after opening a file/asset picker) when and how an item flips between
|
|
28
|
+
* the two states.
|
|
29
|
+
*/
|
|
30
|
+
export type ComparisonBarItem = {
|
|
31
|
+
/**
|
|
32
|
+
* Stable identifier for this slot. Returned to callbacks so the consumer
|
|
33
|
+
* can target the exact item that was interacted with. Falls back to the
|
|
34
|
+
* array index when omitted.
|
|
35
|
+
*/
|
|
36
|
+
id?: string | number
|
|
37
|
+
/**
|
|
38
|
+
* Image to render in the slot. When provided the slot renders in the
|
|
39
|
+
* "Image Added" state; when omitted/`null` it renders the empty "Add" state.
|
|
40
|
+
* Accepts the same shapes as the library `Image` component (remote URL
|
|
41
|
+
* string, `{ uri }`, or a `require()`d asset).
|
|
42
|
+
*/
|
|
43
|
+
imageSource?: ImageSourcePropType | string | null
|
|
44
|
+
/** Accessibility label for the slot. Defaults to a generic add/remove label. */
|
|
45
|
+
accessibilityLabel?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ComparisonBarProps = {
|
|
49
|
+
/**
|
|
50
|
+
* The slots rendered before the Compare button. Each entry controls its own
|
|
51
|
+
* add/image state via {@link ComparisonBarItem.imageSource}.
|
|
52
|
+
*/
|
|
53
|
+
items: ComparisonBarItem[]
|
|
54
|
+
/**
|
|
55
|
+
* Fired when an empty (Add) slot is tapped. The consumer is expected to react
|
|
56
|
+
* by opening whatever picker is appropriate and then updating that item's
|
|
57
|
+
* `imageSource` to flip it into the "Image Added" state — the component does
|
|
58
|
+
* not know how images are sourced. Receives the item's `id` (or index when no
|
|
59
|
+
* id was supplied) and the slot index.
|
|
60
|
+
*/
|
|
61
|
+
onItemPress?: (id: ComparisonBarItem['id'], index: number) => void
|
|
62
|
+
/**
|
|
63
|
+
* Fired when a filled slot is tapped. For better mobile ergonomics the
|
|
64
|
+
* *entire* filled slot is the remove target (the dismiss capsule is just a
|
|
65
|
+
* visual affordance), so a fingertip anywhere on the item triggers this. The
|
|
66
|
+
* consumer is expected to clear that item's `imageSource` to return it to the
|
|
67
|
+
* "Add" state. Receives the item's `id` (or index) and the slot index.
|
|
68
|
+
*/
|
|
69
|
+
onItemRemove?: (id: ComparisonBarItem['id'], index: number) => void
|
|
70
|
+
/** Fired when the Compare button is pressed. */
|
|
71
|
+
onCompare?: () => void
|
|
72
|
+
/** Label for the trailing action button. Defaults to `"Compare"`. */
|
|
73
|
+
compareLabel?: string
|
|
74
|
+
/**
|
|
75
|
+
* Explicitly controls the Compare button's *functional* disabled state — a
|
|
76
|
+
* truly disabled button is non-interactive (its `onPress` never fires), not
|
|
77
|
+
* just dimmed. Note that `modes` only affects appearance, so dimming the
|
|
78
|
+
* button via tokens does NOT stop taps; that is what this prop is for.
|
|
79
|
+
*
|
|
80
|
+
* When provided (a boolean), it always wins over
|
|
81
|
+
* {@link ComparisonBarProps.disableCompareWhenEmpty}. Leave it `undefined`
|
|
82
|
+
* to fall back to the auto behavior.
|
|
83
|
+
*/
|
|
84
|
+
compareDisabled?: boolean
|
|
85
|
+
/**
|
|
86
|
+
* When `true` (default) the Compare button is automatically (and truly)
|
|
87
|
+
* disabled while no slot has an image — there is nothing to compare yet. Set
|
|
88
|
+
* to `false` to keep it tappable even when empty. Ignored when
|
|
89
|
+
* {@link ComparisonBarProps.compareDisabled} is set explicitly.
|
|
90
|
+
*/
|
|
91
|
+
disableCompareWhenEmpty?: boolean
|
|
92
|
+
/** Mode configuration passed to the token resolver. */
|
|
93
|
+
modes?: Modes
|
|
94
|
+
/** Style overrides for the outer floating card. */
|
|
95
|
+
style?: StyleProp<ViewStyle>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ITEM_WIDTH = 45
|
|
99
|
+
const ITEM_HEIGHT = 44
|
|
100
|
+
|
|
101
|
+
interface ComparisonBarTokens {
|
|
102
|
+
card: ViewStyle
|
|
103
|
+
item: ViewStyle
|
|
104
|
+
itemImageState: ViewStyle
|
|
105
|
+
imageRadius: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveTokens(modes: Modes): ComparisonBarTokens {
|
|
109
|
+
const cardGap = (getVariableByName('compareFloatCard/gap', modes) ?? 12) as number
|
|
110
|
+
const cardPadH = (getVariableByName('compareFloatCard/padding/horizontal', modes) ?? 12) as number
|
|
111
|
+
const cardPadV = (getVariableByName('compareFloatCard/padding/vertical', modes) ?? 10) as number
|
|
112
|
+
const cardRadius = (getVariableByName('compareFloatCard/radius', modes) ?? 12) as number
|
|
113
|
+
const cardBackground = (getVariableByName('compareFloatCard/background', modes) ?? '#ffffff') as string
|
|
114
|
+
const cardBorderColor = (getVariableByName('compareFloatCard/border/color', modes) ?? '#f5f5f5') as string
|
|
115
|
+
|
|
116
|
+
const itemPadH = (getVariableByName('compareCardItem/padding/horizontal', modes) ?? 6) as number
|
|
117
|
+
const itemPadV = (getVariableByName('compareCardItem/padding/vertical', modes) ?? 8) as number
|
|
118
|
+
const itemRadius = (getVariableByName('compareCardItem/radius', modes) ?? 8) as number
|
|
119
|
+
const itemBackground = (getVariableByName('compareCardItem/background', modes) ?? '#ebebed') as string
|
|
120
|
+
|
|
121
|
+
const imageRadius = (getVariableByName('image/radius', modes) ?? 8) as number
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
card: {
|
|
125
|
+
flexDirection: 'row',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
gap: cardGap,
|
|
128
|
+
paddingHorizontal: cardPadH,
|
|
129
|
+
paddingVertical: cardPadV,
|
|
130
|
+
borderRadius: cardRadius,
|
|
131
|
+
borderWidth: 1,
|
|
132
|
+
borderColor: cardBorderColor,
|
|
133
|
+
backgroundColor: cardBackground,
|
|
134
|
+
alignSelf: 'flex-start',
|
|
135
|
+
},
|
|
136
|
+
item: {
|
|
137
|
+
width: ITEM_WIDTH,
|
|
138
|
+
height: ITEM_HEIGHT,
|
|
139
|
+
borderRadius: itemRadius,
|
|
140
|
+
backgroundColor: itemBackground,
|
|
141
|
+
paddingHorizontal: itemPadH,
|
|
142
|
+
paddingVertical: itemPadV,
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
},
|
|
146
|
+
itemImageState: {
|
|
147
|
+
paddingHorizontal: itemPadH,
|
|
148
|
+
paddingVertical: itemPadV,
|
|
149
|
+
},
|
|
150
|
+
imageRadius,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type AdditemProps = {
|
|
155
|
+
item: ComparisonBarItem
|
|
156
|
+
index: number
|
|
157
|
+
tokens: ComparisonBarTokens
|
|
158
|
+
addCapsuleModes: Modes
|
|
159
|
+
closeCapsuleModes: Modes
|
|
160
|
+
onPress?: ComparisonBarProps['onItemPress']
|
|
161
|
+
onRemove?: ComparisonBarProps['onItemRemove']
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Internal slot renderer for {@link ComparisonBar}. Intentionally NOT exported
|
|
166
|
+
* — it is meaningless outside of a `ComparisonBar` (its layout, sizing and
|
|
167
|
+
* remove affordance all assume the surrounding card) and is kept private so
|
|
168
|
+
* the public surface stays a single, cohesive component.
|
|
169
|
+
*/
|
|
170
|
+
function Additem({ item, index, tokens, addCapsuleModes, closeCapsuleModes, onPress, onRemove }: AdditemProps) {
|
|
171
|
+
const hasImage = item.imageSource != null && item.imageSource !== ''
|
|
172
|
+
const id = item.id ?? index
|
|
173
|
+
|
|
174
|
+
const addWebProps = usePressableWebSupport({
|
|
175
|
+
restProps: {},
|
|
176
|
+
onPress: () => onPress?.(id, index),
|
|
177
|
+
disabled: false,
|
|
178
|
+
accessibilityLabel: item.accessibilityLabel ?? 'Add item to comparison',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const removeWebProps = usePressableWebSupport({
|
|
182
|
+
restProps: {},
|
|
183
|
+
onPress: () => onRemove?.(id, index),
|
|
184
|
+
disabled: false,
|
|
185
|
+
accessibilityLabel: item.accessibilityLabel ?? 'Remove item from comparison',
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (!hasImage) {
|
|
189
|
+
return (
|
|
190
|
+
<Pressable
|
|
191
|
+
style={tokens.item}
|
|
192
|
+
accessibilityRole="button"
|
|
193
|
+
accessibilityLabel={item.accessibilityLabel ?? 'Add item to comparison'}
|
|
194
|
+
onPress={() => onPress?.(id, index)}
|
|
195
|
+
{...addWebProps}
|
|
196
|
+
>
|
|
197
|
+
<IconCapsule iconName="ic_add" modes={addCapsuleModes} style={ADD_CAPSULE_STYLE} />
|
|
198
|
+
</Pressable>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Mobile-first: the entire filled slot is the remove target, not just the
|
|
203
|
+
// tiny close capsule (which a fingertip struggles to hit). The capsule stays
|
|
204
|
+
// purely as a visual affordance and is marked non-interactive so it never
|
|
205
|
+
// intercepts the press from the surrounding Pressable.
|
|
206
|
+
return (
|
|
207
|
+
<Pressable
|
|
208
|
+
style={[tokens.item, tokens.itemImageState]}
|
|
209
|
+
accessibilityRole="button"
|
|
210
|
+
accessibilityLabel={item.accessibilityLabel ?? 'Remove item from comparison'}
|
|
211
|
+
onPress={() => onRemove?.(id, index)}
|
|
212
|
+
{...removeWebProps}
|
|
213
|
+
>
|
|
214
|
+
<Image
|
|
215
|
+
imageSource={item.imageSource as any}
|
|
216
|
+
width="100%"
|
|
217
|
+
height="100%"
|
|
218
|
+
borderRadius={tokens.imageRadius}
|
|
219
|
+
resizeMode="cover"
|
|
220
|
+
accessibilityLabel={item.accessibilityLabel ?? 'Comparison item image'}
|
|
221
|
+
/>
|
|
222
|
+
<View style={CLOSE_CAPSULE_WRAPPER_STYLE} pointerEvents="none">
|
|
223
|
+
<IconCapsule iconName="ic_close" modes={closeCapsuleModes} />
|
|
224
|
+
</View>
|
|
225
|
+
</Pressable>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The Add capsule is the transparent IconCapsule variant from Figma (the gray
|
|
230
|
+
// item box is the visible surface); its size/icon come from the resolved
|
|
231
|
+
// `Icon Capsule Size: S` tokens, we only flatten the background/border here.
|
|
232
|
+
const ADD_CAPSULE_STYLE: ViewStyle = {
|
|
233
|
+
backgroundColor: 'transparent',
|
|
234
|
+
borderColor: 'transparent',
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Positions the dismiss IconCapsule in the slot's top-right corner. The capsule
|
|
238
|
+
// itself (size/background/icon) is fully token-driven via `closeCapsuleModes`.
|
|
239
|
+
const CLOSE_CAPSULE_WRAPPER_STYLE: ViewStyle = {
|
|
240
|
+
position: 'absolute',
|
|
241
|
+
top: 1,
|
|
242
|
+
right: 1,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Mode overrides applied on top of the consumer's `modes` for each capsule.
|
|
246
|
+
// These mirror the Figma component's IconCapsule variant selections so the
|
|
247
|
+
// sizing and colours come from design tokens instead of magic numbers.
|
|
248
|
+
const ADD_CAPSULE_MODE_OVERRIDES: Modes = { 'Icon Capsule Size': 'XS' }
|
|
249
|
+
const CLOSE_CAPSULE_MODE_OVERRIDES: Modes = {
|
|
250
|
+
AppearanceBrand: 'Neutral',
|
|
251
|
+
Emphasis: 'Medium',
|
|
252
|
+
'Icon Capsule Size': 'XS',
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* ComparisonBar — a floating card that lets a user assemble a set of items to
|
|
257
|
+
* compare, then trigger the comparison.
|
|
258
|
+
*
|
|
259
|
+
* Each slot is fully controlled via its `imageSource`: an empty slot shows a
|
|
260
|
+
* tappable `+` (the "Add" state) and a filled slot shows the image with a
|
|
261
|
+
* dismiss capsule (the "Image Added" state). The component never sources or
|
|
262
|
+
* stores images itself — when an empty slot is pressed it fires `onItemPress`
|
|
263
|
+
* with the item's id/index so the consumer can open whatever picker is
|
|
264
|
+
* appropriate and then update that item's `imageSource` to flip its state.
|
|
265
|
+
* Tapping a filled slot (anywhere on it — a mobile-friendly hit target, with
|
|
266
|
+
* the dismiss capsule as a visual affordance) fires `onItemRemove` so the
|
|
267
|
+
* consumer can clear the source again.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```tsx
|
|
271
|
+
* const [items, setItems] = useState<ComparisonBarItem[]>([
|
|
272
|
+
* { id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'd' },
|
|
273
|
+
* ])
|
|
274
|
+
*
|
|
275
|
+
* <ComparisonBar
|
|
276
|
+
* items={items}
|
|
277
|
+
* onItemPress={async (id) => {
|
|
278
|
+
* const uri = await openImagePicker()
|
|
279
|
+
* setItems(prev => prev.map(it => it.id === id ? { ...it, imageSource: uri } : it))
|
|
280
|
+
* }}
|
|
281
|
+
* onItemRemove={(id) =>
|
|
282
|
+
* setItems(prev => prev.map(it => it.id === id ? { ...it, imageSource: null } : it))
|
|
283
|
+
* }
|
|
284
|
+
* onCompare={runComparison}
|
|
285
|
+
* />
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
function ComparisonBar({
|
|
289
|
+
items,
|
|
290
|
+
onItemPress,
|
|
291
|
+
onItemRemove,
|
|
292
|
+
onCompare,
|
|
293
|
+
compareLabel = 'Compare',
|
|
294
|
+
compareDisabled,
|
|
295
|
+
disableCompareWhenEmpty = true,
|
|
296
|
+
modes: propModes = EMPTY_MODES,
|
|
297
|
+
style,
|
|
298
|
+
}: ComparisonBarProps) {
|
|
299
|
+
const { modes: globalModes } = useTokens()
|
|
300
|
+
const modes = useMemo(
|
|
301
|
+
() =>
|
|
302
|
+
globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
303
|
+
? EMPTY_MODES
|
|
304
|
+
: { ...globalModes, ...propModes },
|
|
305
|
+
[globalModes, propModes]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
const tokens = useMemo(() => resolveTokens(modes), [modes])
|
|
309
|
+
|
|
310
|
+
// Capsule modes = consumer modes + this component's fixed IconCapsule variant
|
|
311
|
+
// selections. Memoized so each `Additem`'s `IconCapsule` keeps a stable
|
|
312
|
+
// `modes` identity and hits the resolver cache.
|
|
313
|
+
const addCapsuleModes = useMemo(
|
|
314
|
+
() => ({ ...modes, ...ADD_CAPSULE_MODE_OVERRIDES }),
|
|
315
|
+
[modes]
|
|
316
|
+
)
|
|
317
|
+
const closeCapsuleModes = useMemo(
|
|
318
|
+
() => ({ ...modes, ...CLOSE_CAPSULE_MODE_OVERRIDES }),
|
|
319
|
+
[modes]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
// An explicit `compareDisabled` always wins (functional disable — the tap is
|
|
323
|
+
// truly blocked, not merely dimmed). Otherwise auto-disable while no slot has
|
|
324
|
+
// an image, since there is nothing to compare yet.
|
|
325
|
+
const hasAnyImage = useMemo(
|
|
326
|
+
() => items.some((it) => it.imageSource != null && it.imageSource !== ''),
|
|
327
|
+
[items]
|
|
328
|
+
)
|
|
329
|
+
const isCompareDisabled =
|
|
330
|
+
compareDisabled ?? (disableCompareWhenEmpty && !hasAnyImage)
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<View style={[tokens.card, style]}>
|
|
334
|
+
{items.map((item, index) => (
|
|
335
|
+
<Additem
|
|
336
|
+
key={item.id ?? index}
|
|
337
|
+
item={item}
|
|
338
|
+
index={index}
|
|
339
|
+
tokens={tokens}
|
|
340
|
+
addCapsuleModes={addCapsuleModes}
|
|
341
|
+
closeCapsuleModes={closeCapsuleModes}
|
|
342
|
+
onPress={onItemPress}
|
|
343
|
+
onRemove={onItemRemove}
|
|
344
|
+
/>
|
|
345
|
+
))}
|
|
346
|
+
<Button
|
|
347
|
+
label={compareLabel}
|
|
348
|
+
modes={modes}
|
|
349
|
+
disabled={isCompareDisabled}
|
|
350
|
+
{...(onCompare !== undefined ? { onPress: onCompare } : {})}
|
|
351
|
+
/>
|
|
352
|
+
</View>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export default React.memo(ComparisonBar)
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import Svg, { Rect } from 'react-native-svg'
|
|
11
11
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
12
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
13
|
-
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
13
|
+
import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
|
|
14
14
|
import MetricLegendItem from '../MetricLegendItem/MetricLegendItem'
|
|
15
15
|
import type { Modes } from '../../design-tokens'
|
|
16
16
|
|
|
@@ -97,6 +97,12 @@ export type CoverageBarComparisonProps = {
|
|
|
97
97
|
labelStyle?: StyleProp<TextStyle>
|
|
98
98
|
/** Accessibility label for the entire component. */
|
|
99
99
|
accessibilityLabel?: string
|
|
100
|
+
/**
|
|
101
|
+
* When `true`, disables text truncation: the trailing ellipsis (`…`) is never
|
|
102
|
+
* shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
|
|
103
|
+
* lines as it needs even when horizontal space is tight.
|
|
104
|
+
*/
|
|
105
|
+
disableTruncation?: boolean
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
@@ -221,6 +227,7 @@ function CoverageBarComparison({
|
|
|
221
227
|
legendStyle,
|
|
222
228
|
labelStyle,
|
|
223
229
|
accessibilityLabel,
|
|
230
|
+
disableTruncation,
|
|
224
231
|
}: CoverageBarComparisonProps) {
|
|
225
232
|
const { modes: globalModes } = useTokens()
|
|
226
233
|
const modes = React.useMemo(
|
|
@@ -331,7 +338,7 @@ function CoverageBarComparison({
|
|
|
331
338
|
>
|
|
332
339
|
{hasLabel ? (
|
|
333
340
|
<Text
|
|
334
|
-
|
|
341
|
+
{...resolveTruncation(disableTruncation, 1)}
|
|
335
342
|
style={[computedLabelStyle, labelStyle]}
|
|
336
343
|
>
|
|
337
344
|
{original.label}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import Svg, { Circle } from 'react-native-svg'
|
|
11
11
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
12
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
13
|
-
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
|
|
13
|
+
import { EMPTY_MODES, flattenChildren, resolveTruncation } from '../../utils/react-utils'
|
|
14
14
|
import type { Modes } from '../../design-tokens'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -83,6 +83,12 @@ export type DonutChartProps = {
|
|
|
83
83
|
labelStyle?: StyleProp<TextStyle>
|
|
84
84
|
/** Accessibility label announced for the whole donut chart. */
|
|
85
85
|
accessibilityLabel?: string
|
|
86
|
+
/**
|
|
87
|
+
* When `true`, disables text truncation: the trailing ellipsis (`…`) is never
|
|
88
|
+
* shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
|
|
89
|
+
* lines as it needs even when horizontal space is tight.
|
|
90
|
+
*/
|
|
91
|
+
disableTruncation?: boolean
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
const DEFAULT_APPEARANCE_CYCLE = [
|
|
@@ -222,6 +228,7 @@ function DonutChart({
|
|
|
222
228
|
valueStyle,
|
|
223
229
|
labelStyle,
|
|
224
230
|
accessibilityLabel,
|
|
231
|
+
disableTruncation,
|
|
225
232
|
}: DonutChartProps) {
|
|
226
233
|
const { modes: globalModes } = useTokens()
|
|
227
234
|
const modes = { ...globalModes, ...propModes }
|
|
@@ -388,7 +395,7 @@ function DonutChart({
|
|
|
388
395
|
},
|
|
389
396
|
valueStyle,
|
|
390
397
|
]}
|
|
391
|
-
|
|
398
|
+
{...resolveTruncation(disableTruncation, 1)}
|
|
392
399
|
>
|
|
393
400
|
{value}
|
|
394
401
|
</Text>
|
|
@@ -406,7 +413,7 @@ function DonutChart({
|
|
|
406
413
|
},
|
|
407
414
|
labelStyle,
|
|
408
415
|
]}
|
|
409
|
-
|
|
416
|
+
{...resolveTruncation(disableTruncation, 1)}
|
|
410
417
|
>
|
|
411
418
|
{label}
|
|
412
419
|
</Text>
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from 'react-native'
|
|
14
14
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
15
15
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
16
|
-
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
16
|
+
import { EMPTY_MODES, cloneChildrenWithModes, resolveTruncation } from '../../utils/react-utils'
|
|
17
17
|
import Icon from '../../icons/Icon'
|
|
18
18
|
import type { Modes } from '../../design-tokens'
|
|
19
19
|
|
|
@@ -57,6 +57,12 @@ export type DropdownItemProps = {
|
|
|
57
57
|
accessibilityLabel?: string
|
|
58
58
|
/** Accessibility hint. */
|
|
59
59
|
accessibilityHint?: string
|
|
60
|
+
/**
|
|
61
|
+
* When `true`, disables text truncation: the trailing ellipsis (`…`) is never
|
|
62
|
+
* shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
|
|
63
|
+
* lines as it needs even when horizontal space is tight.
|
|
64
|
+
*/
|
|
65
|
+
disableTruncation?: boolean
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
function useDropdownItemTokens(modes: Modes) {
|
|
@@ -128,6 +134,7 @@ export function DropdownItem({
|
|
|
128
134
|
labelStyle,
|
|
129
135
|
accessibilityLabel,
|
|
130
136
|
accessibilityHint,
|
|
137
|
+
disableTruncation,
|
|
131
138
|
}: DropdownItemProps) {
|
|
132
139
|
const { modes: globalModes } = useTokens()
|
|
133
140
|
const modes = useMemo(
|
|
@@ -226,7 +233,7 @@ export function DropdownItem({
|
|
|
226
233
|
{children != null ? (
|
|
227
234
|
children
|
|
228
235
|
) : (
|
|
229
|
-
<Text style={[textStyle, labelStyle]}
|
|
236
|
+
<Text style={[textStyle, labelStyle]} {...resolveTruncation(disableTruncation, 1)}>
|
|
230
237
|
{label}
|
|
231
238
|
</Text>
|
|
232
239
|
)}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, {
|
|
2
|
+
forwardRef,
|
|
2
3
|
useCallback,
|
|
3
4
|
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
4
6
|
useMemo,
|
|
5
7
|
useRef,
|
|
6
8
|
useState,
|
|
@@ -22,7 +24,7 @@ import {
|
|
|
22
24
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
23
25
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
24
26
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
25
|
-
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
|
|
27
|
+
import { EMPTY_MODES, flattenChildren, resolveTruncation } from '../../utils/react-utils'
|
|
26
28
|
import Icon from '../../icons/Icon'
|
|
27
29
|
import SupportText from '../SupportText/SupportText'
|
|
28
30
|
import type { SupportTextStatus } from '../SupportText/SupportTextIcon'
|
|
@@ -52,6 +54,28 @@ export type DropdownInputOption = {
|
|
|
52
54
|
|
|
53
55
|
type Rect = { x: number; y: number; width: number; height: number }
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Imperative handle exposed via `ref`. Lets the consumer drive the dropdown
|
|
59
|
+
* from outside — e.g. close it when an Android hardware/system button is
|
|
60
|
+
* pressed, or open it programmatically from a sibling control.
|
|
61
|
+
*/
|
|
62
|
+
export type DropdownInputHandle = {
|
|
63
|
+
/** Opens the options menu (no-op when disabled / read-only). */
|
|
64
|
+
open: () => void
|
|
65
|
+
/** Closes the options menu. */
|
|
66
|
+
close: () => void
|
|
67
|
+
/** Toggles the options menu open/closed. */
|
|
68
|
+
toggle: () => void
|
|
69
|
+
/** Moves focus to the trigger (web). */
|
|
70
|
+
focus: () => void
|
|
71
|
+
/** Removes focus from the trigger (web). */
|
|
72
|
+
blur: () => void
|
|
73
|
+
/** Measures the trigger in window coordinates. */
|
|
74
|
+
measureInWindow: (
|
|
75
|
+
callback: (x: number, y: number, width: number, height: number) => void
|
|
76
|
+
) => void
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
export type DropdownInputProps = {
|
|
56
80
|
/** Label rendered above the input. */
|
|
57
81
|
label?: string
|
|
@@ -147,6 +171,12 @@ export type DropdownInputProps = {
|
|
|
147
171
|
onFocus?: (e: any) => void
|
|
148
172
|
/** Called when the trigger loses focus (web only). */
|
|
149
173
|
onBlur?: (e: any) => void
|
|
174
|
+
/**
|
|
175
|
+
* When `true`, disables text truncation: the trailing ellipsis (`…`) is never
|
|
176
|
+
* shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
|
|
177
|
+
* lines as it needs even when horizontal space is tight.
|
|
178
|
+
*/
|
|
179
|
+
disableTruncation?: boolean
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
// ---------------------------------------------------------------------------
|
|
@@ -307,7 +337,7 @@ function collectOptionsFromChildren(
|
|
|
307
337
|
// Component
|
|
308
338
|
// ---------------------------------------------------------------------------
|
|
309
339
|
|
|
310
|
-
function DropdownInput({
|
|
340
|
+
const DropdownInput = forwardRef<DropdownInputHandle, DropdownInputProps>(function DropdownInput({
|
|
311
341
|
label,
|
|
312
342
|
placeholder = 'Select an option',
|
|
313
343
|
items,
|
|
@@ -338,7 +368,8 @@ function DropdownInput({
|
|
|
338
368
|
accessibilityHint,
|
|
339
369
|
onFocus,
|
|
340
370
|
onBlur,
|
|
341
|
-
|
|
371
|
+
disableTruncation,
|
|
372
|
+
}: DropdownInputProps, ref: React.Ref<DropdownInputHandle>) {
|
|
342
373
|
// ---------------- Modes ----------------
|
|
343
374
|
const { modes: globalModes } = useTokens()
|
|
344
375
|
const baseModes = useMemo(
|
|
@@ -497,6 +528,34 @@ function DropdownInput({
|
|
|
497
528
|
[measure]
|
|
498
529
|
)
|
|
499
530
|
|
|
531
|
+
// ---------------- Imperative handle ----------------
|
|
532
|
+
useImperativeHandle(
|
|
533
|
+
ref,
|
|
534
|
+
() => ({
|
|
535
|
+
open: () => {
|
|
536
|
+
if (isDisabled || isReadOnly) return
|
|
537
|
+
measure()
|
|
538
|
+
setOpenState(true)
|
|
539
|
+
},
|
|
540
|
+
close: closeMenu,
|
|
541
|
+
toggle: () => {
|
|
542
|
+
if (isDisabled || isReadOnly) return
|
|
543
|
+
measure()
|
|
544
|
+
toggleMenu()
|
|
545
|
+
},
|
|
546
|
+
focus: () => {
|
|
547
|
+
;(triggerRef.current as unknown as { focus?: () => void })?.focus?.()
|
|
548
|
+
},
|
|
549
|
+
blur: () => {
|
|
550
|
+
;(triggerRef.current as unknown as { blur?: () => void })?.blur?.()
|
|
551
|
+
},
|
|
552
|
+
measureInWindow: (callback) => {
|
|
553
|
+
triggerRef.current?.measureInWindow(callback)
|
|
554
|
+
},
|
|
555
|
+
}),
|
|
556
|
+
[isDisabled, isReadOnly, measure, setOpenState, closeMenu, toggleMenu]
|
|
557
|
+
)
|
|
558
|
+
|
|
500
559
|
// ---------------- Popup positioning ----------------
|
|
501
560
|
const [menuSize, setMenuSize] = useState<{
|
|
502
561
|
width: number
|
|
@@ -777,7 +836,7 @@ function DropdownInput({
|
|
|
777
836
|
valueTextStyle,
|
|
778
837
|
!hasValue && { color: placeholderColor },
|
|
779
838
|
]}
|
|
780
|
-
|
|
839
|
+
{...resolveTruncation(disableTruncation, 1)}
|
|
781
840
|
>
|
|
782
841
|
{displayLabel}
|
|
783
842
|
</Text>
|
|
@@ -863,7 +922,7 @@ function DropdownInput({
|
|
|
863
922
|
</Modal>
|
|
864
923
|
</View>
|
|
865
924
|
)
|
|
866
|
-
}
|
|
925
|
+
})
|
|
867
926
|
|
|
868
927
|
const webNoOutline: any = {
|
|
869
928
|
outlineStyle: 'none',
|
|
@@ -35,6 +35,12 @@ export type ExpandableCheckboxProps = {
|
|
|
35
35
|
readLessLabel?: string
|
|
36
36
|
/** Number of lines to show when collapsed. Defaults to `1`. */
|
|
37
37
|
collapsedLines?: number
|
|
38
|
+
/**
|
|
39
|
+
* When `true`, disables truncation of the collapsed label: the trailing
|
|
40
|
+
* ellipsis (`…`) is never shown and the `collapsedLines` clamp is ignored, so
|
|
41
|
+
* the label wraps fully even when horizontal space is tight.
|
|
42
|
+
*/
|
|
43
|
+
disableTruncation?: boolean
|
|
38
44
|
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
39
45
|
modes?: Modes
|
|
40
46
|
/** Override outer container styles. */
|
|
@@ -96,6 +102,7 @@ function ExpandableCheckbox({
|
|
|
96
102
|
readMoreLabel = 'Read more',
|
|
97
103
|
readLessLabel = 'Read less',
|
|
98
104
|
collapsedLines = 1,
|
|
105
|
+
disableTruncation = false,
|
|
99
106
|
modes = EMPTY_MODES,
|
|
100
107
|
style,
|
|
101
108
|
labelStyle,
|
|
@@ -205,7 +212,7 @@ function ExpandableCheckbox({
|
|
|
205
212
|
const buttonLabel = isExpanded ? readLessLabel : readMoreLabel
|
|
206
213
|
|
|
207
214
|
const labelNumberOfLinesProps =
|
|
208
|
-
!isExpanded && collapsedLines > 0
|
|
215
|
+
!disableTruncation && !isExpanded && collapsedLines > 0
|
|
209
216
|
? { numberOfLines: collapsedLines, ellipsizeMode: 'tail' as const }
|
|
210
217
|
: null
|
|
211
218
|
|