jfs-components 0.0.3 → 0.0.5

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.
Files changed (128) hide show
  1. package/lib/commonjs/Containers.js +16 -7
  2. package/lib/commonjs/Containers.js.map +1 -1
  3. package/lib/commonjs/components/Accordion/Accordion.js +217 -0
  4. package/lib/commonjs/components/Accordion/Accordion.js.map +1 -0
  5. package/lib/commonjs/components/Accordion/Accordion.mdx +123 -0
  6. package/lib/commonjs/components/ActionFooter/ActionFooter.mdx +1 -0
  7. package/lib/commonjs/components/ActionTile/ActionTile.js +93 -0
  8. package/lib/commonjs/components/ActionTile/ActionTile.js.map +1 -0
  9. package/lib/commonjs/components/ActionTile/ActionTile.mdx +54 -0
  10. package/lib/commonjs/components/ActionTile/index.js +14 -0
  11. package/lib/commonjs/components/ActionTile/index.js.map +1 -0
  12. package/lib/commonjs/components/CtaCard/CtaCard.js +122 -0
  13. package/lib/commonjs/components/CtaCard/CtaCard.js.map +1 -0
  14. package/lib/commonjs/components/CtaCard/CtaCard.mdx +65 -0
  15. package/lib/commonjs/components/CtaCard/index.js +14 -0
  16. package/lib/commonjs/components/CtaCard/index.js.map +1 -0
  17. package/lib/commonjs/components/Introduction.mdx +0 -1
  18. package/lib/commonjs/components/ThreadHero/ThreadHero.js +114 -0
  19. package/lib/commonjs/components/ThreadHero/ThreadHero.js.map +1 -0
  20. package/lib/commonjs/components/ThreadHero/ThreadHero.mdx +69 -0
  21. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +101 -0
  22. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js.map +1 -0
  23. package/lib/commonjs/components/TransactionBubble/TransactionBubble.mdx +40 -0
  24. package/lib/commonjs/components/TransactionDetails/TransactionDetails.js +177 -0
  25. package/lib/commonjs/components/TransactionDetails/TransactionDetails.js.map +1 -0
  26. package/lib/commonjs/components/TransactionDetails/TransactionDetails.mdx +98 -0
  27. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +82 -0
  28. package/lib/commonjs/components/TransactionStatus/TransactionStatus.js.map +1 -0
  29. package/lib/commonjs/components/TransactionStatus/TransactionStatus.mdx +41 -0
  30. package/lib/commonjs/components/index.js +35 -0
  31. package/lib/commonjs/components/index.js.map +1 -1
  32. package/lib/commonjs/design-tokens/JFSThemeProvider.js +64 -0
  33. package/lib/commonjs/design-tokens/JFSThemeProvider.js.map +1 -0
  34. package/lib/commonjs/design-tokens/figma-variables-resolver.js +3 -9
  35. package/lib/commonjs/design-tokens/index.js +11 -0
  36. package/lib/commonjs/design-tokens/index.js.map +1 -1
  37. package/lib/commonjs/icons/registry.js +1 -1
  38. package/lib/module/Containers.js +15 -7
  39. package/lib/module/Containers.js.map +1 -1
  40. package/lib/module/components/Accordion/Accordion.js +212 -0
  41. package/lib/module/components/Accordion/Accordion.js.map +1 -0
  42. package/lib/module/components/Accordion/Accordion.mdx +123 -0
  43. package/lib/module/components/ActionFooter/ActionFooter.mdx +1 -0
  44. package/lib/module/components/ActionTile/ActionTile.js +88 -0
  45. package/lib/module/components/ActionTile/ActionTile.js.map +1 -0
  46. package/lib/module/components/ActionTile/ActionTile.mdx +54 -0
  47. package/lib/module/components/ActionTile/index.js +4 -0
  48. package/lib/module/components/ActionTile/index.js.map +1 -0
  49. package/lib/module/components/CtaCard/CtaCard.js +117 -0
  50. package/lib/module/components/CtaCard/CtaCard.js.map +1 -0
  51. package/lib/module/components/CtaCard/CtaCard.mdx +65 -0
  52. package/lib/module/components/CtaCard/index.js +4 -0
  53. package/lib/module/components/CtaCard/index.js.map +1 -0
  54. package/lib/module/components/Introduction.mdx +0 -1
  55. package/lib/module/components/ThreadHero/ThreadHero.js +109 -0
  56. package/lib/module/components/ThreadHero/ThreadHero.js.map +1 -0
  57. package/lib/module/components/ThreadHero/ThreadHero.mdx +69 -0
  58. package/lib/module/components/TransactionBubble/TransactionBubble.js +96 -0
  59. package/lib/module/components/TransactionBubble/TransactionBubble.js.map +1 -0
  60. package/lib/module/components/TransactionBubble/TransactionBubble.mdx +40 -0
  61. package/lib/module/components/TransactionDetails/TransactionDetails.js +174 -0
  62. package/lib/module/components/TransactionDetails/TransactionDetails.js.map +1 -0
  63. package/lib/module/components/TransactionDetails/TransactionDetails.mdx +98 -0
  64. package/lib/module/components/TransactionStatus/TransactionStatus.js +77 -0
  65. package/lib/module/components/TransactionStatus/TransactionStatus.js.map +1 -0
  66. package/lib/module/components/TransactionStatus/TransactionStatus.mdx +41 -0
  67. package/lib/module/components/index.js +5 -0
  68. package/lib/module/components/index.js.map +1 -1
  69. package/lib/module/design-tokens/JFSThemeProvider.js +57 -0
  70. package/lib/module/design-tokens/JFSThemeProvider.js.map +1 -0
  71. package/lib/module/design-tokens/index.js +1 -0
  72. package/lib/module/design-tokens/index.js.map +1 -1
  73. package/lib/module/icons/registry.js +1 -1
  74. package/lib/typescript/Containers.d.ts +2 -1
  75. package/lib/typescript/Containers.d.ts.map +1 -1
  76. package/lib/typescript/components/Accordion/Accordion.d.ts +58 -0
  77. package/lib/typescript/components/Accordion/Accordion.d.ts.map +1 -0
  78. package/lib/typescript/components/ActionTile/ActionTile.d.ts +26 -0
  79. package/lib/typescript/components/ActionTile/ActionTile.d.ts.map +1 -0
  80. package/lib/typescript/components/ActionTile/index.d.ts +2 -0
  81. package/lib/typescript/components/ActionTile/index.d.ts.map +1 -0
  82. package/lib/typescript/components/CtaCard/CtaCard.d.ts +61 -0
  83. package/lib/typescript/components/CtaCard/CtaCard.d.ts.map +1 -0
  84. package/lib/typescript/components/CtaCard/index.d.ts +2 -0
  85. package/lib/typescript/components/CtaCard/index.d.ts.map +1 -0
  86. package/lib/typescript/components/ThreadHero/ThreadHero.d.ts +21 -0
  87. package/lib/typescript/components/ThreadHero/ThreadHero.d.ts.map +1 -0
  88. package/lib/typescript/components/TransactionBubble/TransactionBubble.d.ts +24 -0
  89. package/lib/typescript/components/TransactionBubble/TransactionBubble.d.ts.map +1 -0
  90. package/lib/typescript/components/TransactionDetails/TransactionDetails.d.ts +57 -0
  91. package/lib/typescript/components/TransactionDetails/TransactionDetails.d.ts.map +1 -0
  92. package/lib/typescript/components/TransactionStatus/TransactionStatus.d.ts +16 -0
  93. package/lib/typescript/components/TransactionStatus/TransactionStatus.d.ts.map +1 -0
  94. package/lib/typescript/components/index.d.ts +5 -0
  95. package/lib/typescript/components/index.d.ts.map +1 -1
  96. package/lib/typescript/design-tokens/JFSThemeProvider.d.ts +44 -0
  97. package/lib/typescript/design-tokens/JFSThemeProvider.d.ts.map +1 -0
  98. package/lib/typescript/design-tokens/index.d.ts +1 -0
  99. package/lib/typescript/design-tokens/index.d.ts.map +1 -1
  100. package/lib/typescript/icons/registry.d.ts +1 -1
  101. package/package.json +3 -4
  102. package/src/Containers.tsx +15 -0
  103. package/src/components/.token-metadata.json +161 -0
  104. package/src/components/Accordion/Accordion.mdx +123 -0
  105. package/src/components/Accordion/Accordion.tsx +279 -0
  106. package/src/components/ActionFooter/ActionFooter.mdx +1 -0
  107. package/src/components/ActionTile/ActionTile.mdx +54 -0
  108. package/src/components/ActionTile/ActionTile.tsx +100 -0
  109. package/src/components/ActionTile/index.ts +1 -0
  110. package/src/components/CtaCard/CtaCard.mdx +65 -0
  111. package/src/components/CtaCard/CtaCard.tsx +185 -0
  112. package/src/components/CtaCard/index.ts +1 -0
  113. package/src/components/Divider/Divider.tsx +1 -0
  114. package/src/components/Introduction.mdx +0 -1
  115. package/src/components/NavArrow/NavArrow.tsx +1 -0
  116. package/src/components/ThreadHero/ThreadHero.mdx +69 -0
  117. package/src/components/ThreadHero/ThreadHero.tsx +124 -0
  118. package/src/components/TransactionBubble/TransactionBubble.mdx +40 -0
  119. package/src/components/TransactionBubble/TransactionBubble.tsx +113 -0
  120. package/src/components/TransactionDetails/TransactionDetails.mdx +98 -0
  121. package/src/components/TransactionDetails/TransactionDetails.tsx +236 -0
  122. package/src/components/TransactionStatus/TransactionStatus.mdx +41 -0
  123. package/src/components/TransactionStatus/TransactionStatus.tsx +94 -0
  124. package/src/components/index.ts +5 -0
  125. package/src/design-tokens/JFSThemeProvider.tsx +79 -0
  126. package/src/design-tokens/index.ts +1 -0
  127. package/src/icons/registry.ts +1 -1
  128. package/src/Containers.ts +0 -8
@@ -1,4 +1,35 @@
1
1
  {
2
+ "Accordion": {
3
+ "tokens": [
4
+ "accordion/border/color",
5
+ "accordion/content/gap",
6
+ "accordion/content/padding/bottom",
7
+ "accordion/content/padding/top",
8
+ "accordion/header/background",
9
+ "accordion/header/gap",
10
+ "accordion/header/padding/vertical",
11
+ "accordion/icon/color",
12
+ "accordion/icon/size",
13
+ "accordion/title/color",
14
+ "accordion/title/fontFamily",
15
+ "accordion/title/fontSize",
16
+ "accordion/title/lineHeight"
17
+ ],
18
+ "collections": {
19
+ "Accordion States": {
20
+ "modes": [
21
+ "Idle",
22
+ "Hover",
23
+ "Open",
24
+ "Open Hover",
25
+ "Disabled"
26
+ ],
27
+ "defaultMode": "Idle",
28
+ "hasMultipleModes": true
29
+ }
30
+ },
31
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/Accordion/Accordion.tsx"
32
+ },
2
33
  "ActionFooter": {
3
34
  "tokens": [
4
35
  "actionFooter/background",
@@ -10,6 +41,31 @@
10
41
  "collections": {},
11
42
  "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/ActionFooter/ActionFooter.tsx"
12
43
  },
44
+ "ActionTile": {
45
+ "tokens": [
46
+ "actionTile/background",
47
+ "actionTile/fontFamily",
48
+ "actionTile/fontSize",
49
+ "actionTile/fontWeight",
50
+ "actionTile/foreground",
51
+ "actionTile/gap",
52
+ "actionTile/lineHeight",
53
+ "actionTile/padding/horizontal",
54
+ "actionTile/padding/vertical",
55
+ "actionTile/radius"
56
+ ],
57
+ "collections": {
58
+ "Color Mode": {
59
+ "modes": [
60
+ "Light",
61
+ "Dark"
62
+ ],
63
+ "defaultMode": "Light",
64
+ "hasMultipleModes": true
65
+ }
66
+ },
67
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/ActionTile/ActionTile.tsx"
68
+ },
13
69
  "AppBar": {
14
70
  "tokens": [
15
71
  "appBarActions/actions/gap",
@@ -317,6 +373,26 @@
317
373
  },
318
374
  "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/CardFeedback/CardFeedback.tsx"
319
375
  },
376
+ "CtaCard": {
377
+ "tokens": [
378
+ "ctaCard/background",
379
+ "ctaCard/body/color",
380
+ "ctaCard/body/fontFamily",
381
+ "ctaCard/body/fontSize",
382
+ "ctaCard/body/lineHeight",
383
+ "ctaCard/content/gap",
384
+ "ctaCard/gap",
385
+ "ctaCard/padding/horizontal",
386
+ "ctaCard/padding/vertical",
387
+ "ctaCard/radius",
388
+ "ctaCard/title/color",
389
+ "ctaCard/title/fontFamily",
390
+ "ctaCard/title/fontSize",
391
+ "ctaCard/title/lineHeight"
392
+ ],
393
+ "collections": {},
394
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/CtaCard/CtaCard.tsx"
395
+ },
320
396
  "Disclaimer": {
321
397
  "tokens": [
322
398
  "disclaimer/color",
@@ -852,6 +928,29 @@
852
928
  },
853
929
  "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/TextInput/TextInput.tsx"
854
930
  },
931
+ "ThreadHero": {
932
+ "tokens": [
933
+ "threadHero/caption/color",
934
+ "threadHero/caption/fontFamily",
935
+ "threadHero/caption/fontSize",
936
+ "threadHero/caption/fontWeight",
937
+ "threadHero/caption/lineHeight",
938
+ "threadHero/details/gap",
939
+ "threadHero/gap",
940
+ "threadHero/subtitle/color",
941
+ "threadHero/subtitle/fontFamily",
942
+ "threadHero/subtitle/fontSize",
943
+ "threadHero/subtitle/fontWeight",
944
+ "threadHero/subtitle/lineHeight",
945
+ "threadHero/title/color",
946
+ "threadHero/title/fontFamily",
947
+ "threadHero/title/fontSize",
948
+ "threadHero/title/fontWeight",
949
+ "threadHero/title/lineHeight"
950
+ ],
951
+ "collections": {},
952
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/ThreadHero/ThreadHero.tsx"
953
+ },
855
954
  "Tooltip": {
856
955
  "tokens": [
857
956
  "maxWidth",
@@ -879,6 +978,68 @@
879
978
  },
880
979
  "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/Tooltip/Tooltip.tsx"
881
980
  },
981
+ "TransactionBubble": {
982
+ "tokens": [
983
+ "transactionBubble/background",
984
+ "transactionBubble/border/color",
985
+ "transactionBubble/border/size",
986
+ "transactionBubble/description/color",
987
+ "transactionBubble/description/fontFamily",
988
+ "transactionBubble/description/fontSize",
989
+ "transactionBubble/description/lineHeight",
990
+ "transactionBubble/gap",
991
+ "transactionBubble/padding",
992
+ "transactionBubble/radius",
993
+ "transactionBubble/statusWrap/height"
994
+ ],
995
+ "collections": {},
996
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/TransactionBubble/TransactionBubble.tsx"
997
+ },
998
+ "TransactionDetails": {
999
+ "tokens": [
1000
+ "detailItem/gap",
1001
+ "detailItem/label/color",
1002
+ "detailItem/label/fontFamily",
1003
+ "detailItem/label/fontSize",
1004
+ "detailItem/label/fontWeight",
1005
+ "detailItem/label/lineHeight",
1006
+ "detailItem/value/color",
1007
+ "detailItem/value/fontFamily",
1008
+ "detailItem/value/fontSize",
1009
+ "detailItem/value/fontWeight",
1010
+ "detailItem/value/lineHeight",
1011
+ "transationDetails/gap",
1012
+ "transationDetails/padding/horizontal",
1013
+ "transationDetails/padding/vertical"
1014
+ ],
1015
+ "collections": {},
1016
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/TransactionDetails/TransactionDetails.tsx"
1017
+ },
1018
+ "TransactionStatus": {
1019
+ "tokens": [
1020
+ "transactionBubble/statusWrap/gap",
1021
+ "transactionStatus/color",
1022
+ "transactionStatus/fontFamily",
1023
+ "transactionStatus/fontSize",
1024
+ "transactionStatus/fontWeight2",
1025
+ "transactionStatus/icon/color",
1026
+ "transactionStatus/iconSize",
1027
+ "transactionStatus/lineHeight"
1028
+ ],
1029
+ "collections": {
1030
+ "Transaction Status": {
1031
+ "modes": [
1032
+ "Request",
1033
+ "Paid",
1034
+ "Declined",
1035
+ "Expired"
1036
+ ],
1037
+ "defaultMode": "Request",
1038
+ "hasMultipleModes": true
1039
+ }
1040
+ },
1041
+ "filePath": "/Users/sunshuaiqi/jfs/react-native-storybook-boilerplate-master/src/components/TransactionStatus/TransactionStatus.tsx"
1042
+ },
882
1043
  "UpiHandle": {
883
1044
  "tokens": [
884
1045
  "upiHandle/background",
@@ -0,0 +1,123 @@
1
+ import { Meta, Story, Canvas, PureArgsTable as ArgsTable } from '@storybook/addon-docs/blocks';
2
+ import * as AccordionStories from './Accordion.stories';
3
+ import Accordion from './Accordion';
4
+ import { AccessibilitySection, AnatomySection, UsageConstraintsSection } from '../docs/DocSections';
5
+
6
+ <Meta of={AccordionStories} />
7
+
8
+ # Accordion
9
+
10
+ Accordion component that mirrors the Figma "Accordion" component. It provides an expandable/collapsible container for content with smooth animation.
11
+
12
+ ## Available Collections and Modes
13
+
14
+ This component uses the following design token collections. Each collection supports multiple modes that can be configured via the `modes` prop.
15
+
16
+ ### Accordion States
17
+ - **Modes:** Idle | Hover | Open | Open Hover | Disabled
18
+ - **Default:** Idle
19
+ ## Usage
20
+
21
+ <Canvas>
22
+ <Story of={AccordionStories.Default} />
23
+ </Canvas>
24
+
25
+ ## With ListItem Content
26
+
27
+ <Canvas>
28
+ <Story of={AccordionStories.WithListItems} />
29
+ </Canvas>
30
+
31
+ ## Accordion Group
32
+
33
+ <Canvas>
34
+ <Story of={AccordionStories.AccordionGroup} />
35
+ </Canvas>
36
+
37
+ <AccessibilitySection
38
+ items={[
39
+ 'The accordion header has `accessibilityRole="button"` for proper screen reader announcement.',
40
+ 'The `accessibilityState.expanded` property indicates the current state to assistive technology.',
41
+ 'When disabled, `accessibilityState.disabled` is set to true.',
42
+ 'Custom `accessibilityLabel` and `accessibilityHint` can be provided for better context.',
43
+ 'Content within the accordion should maintain its own accessibility attributes.',
44
+ ]}
45
+ />
46
+
47
+ <AnatomySection>
48
+ <ul>
49
+ <li><strong>Header</strong> — Pressable area containing the title and expand/collapse icon (plus/minus).</li>
50
+ <li><strong>Content Slot</strong> — Expandable area that shows/hides with animation. Pass any React children.</li>
51
+ <li><strong>Border</strong> — Bottom border separating accordions when stacked.</li>
52
+ </ul>
53
+ </AnatomySection>
54
+
55
+ <UsageConstraintsSection
56
+ items={[
57
+ 'Use the `modes` prop to ensure consistent theming with surrounding components.',
58
+ 'When nesting components in the content slot, they automatically receive the parent modes via cloning.',
59
+ 'Avoid deeply nested interactive elements that may confuse keyboard navigation.',
60
+ 'For controlled behavior, provide both `expanded` and `onExpandedChange` props.',
61
+ 'Keep accordion titles concise; they are truncated to a single line.',
62
+ ]}
63
+ />
64
+
65
+ ## Props
66
+
67
+ <ArgsTable of={Accordion} />
68
+
69
+ ## Design Tokens
70
+
71
+ This component uses the following design tokens, resolved through `getVariableByName`:
72
+
73
+ - **`accordion/border/color`**
74
+ - **`accordion/content/gap`**
75
+ - **`accordion/content/padding/bottom`**
76
+ - **`accordion/content/padding/top`**
77
+ - **`accordion/header/background`**
78
+ - **`accordion/header/gap`**
79
+ - **`accordion/header/padding/vertical`**
80
+ - **`accordion/icon/color`**
81
+ - **`accordion/icon/size`**
82
+ - **`accordion/title/color`**
83
+ - **`accordion/title/fontFamily`**
84
+ - **`accordion/title/fontSize`**
85
+ - **`accordion/title/lineHeight`**
86
+
87
+ All tokens support mode-based theming through the `modes` prop.
88
+ ## Example Usage
89
+
90
+ ```tsx
91
+ import { Accordion, ListItem, IconCapsule, MoneyValue } from '@jsf/components';
92
+
93
+ function PaymentSection() {
94
+ return (
95
+ <Accordion
96
+ title="Payment Methods"
97
+ defaultExpanded={true}
98
+ modes={{ 'Color Mode': 'Light' }}
99
+ >
100
+ <ListItem
101
+ layout="Horizontal"
102
+ title="Credit Card"
103
+ supportText="Ending in 4242"
104
+ leading={<IconCapsule iconName="ic_card" />}
105
+ endSlot={<MoneyValue value="500" currency="₹" />}
106
+ navArrow={true}
107
+ />
108
+ <ListItem
109
+ layout="Horizontal"
110
+ title="Savings Account"
111
+ supportText="HDFC Bank"
112
+ leading={<IconCapsule iconName="ic_bank_account" />}
113
+ endSlot={<MoneyValue value="25,000" currency="₹" />}
114
+ navArrow={true}
115
+ />
116
+ </Accordion>
117
+ );
118
+ }
119
+ ```
120
+
121
+
122
+
123
+
@@ -0,0 +1,279 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ Pressable,
6
+ LayoutAnimation,
7
+ Platform,
8
+ UIManager,
9
+ type StyleProp,
10
+ type ViewStyle,
11
+ type TextStyle,
12
+ type AccessibilityState,
13
+ } from 'react-native'
14
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
15
+ import Icon from '../../icons/Icon'
16
+ import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
17
+
18
+ // Enable LayoutAnimation on Android
19
+ if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
20
+ UIManager.setLayoutAnimationEnabledExperimental(true)
21
+ }
22
+
23
+ /**
24
+ * Helper function to recursively clone children and pass modes prop to components that accept it.
25
+ * This ensures that all child components in slots receive the modes prop from the parent.
26
+ */
27
+ function cloneChildrenWithModes(
28
+ children: React.ReactNode,
29
+ modes: Record<string, any>
30
+ ): React.ReactNode[] {
31
+ const result = React.Children.map(children, (child) => {
32
+ if (!React.isValidElement(child)) {
33
+ return child
34
+ }
35
+
36
+ // Get existing children
37
+ const childChildren = (child.props as any)?.children
38
+ const hasChildren = childChildren !== undefined && childChildren !== null
39
+
40
+ // Merge modes: parent modes first, then child's explicit modes override them
41
+ const existingModes = (child.props as any)?.modes
42
+ const mergedModes = existingModes
43
+ ? { ...modes, ...existingModes }
44
+ : modes
45
+
46
+ // Recursively process children if they exist
47
+ const processedChildren: React.ReactNode | undefined = hasChildren
48
+ ? cloneChildrenWithModes(
49
+ React.Children.toArray(childChildren),
50
+ modes
51
+ )
52
+ : undefined
53
+
54
+ // Clone element with modes and processed children
55
+ return React.cloneElement(
56
+ child,
57
+ {
58
+ ...(child.props as any),
59
+ modes: mergedModes,
60
+ },
61
+ processedChildren
62
+ )
63
+ })
64
+ return result || []
65
+ }
66
+
67
+ export type AccordionProps = {
68
+ /** The accordion header title */
69
+ title?: string;
70
+ /** Initial expanded state. Defaults to false (collapsed) */
71
+ defaultExpanded?: boolean;
72
+ /** Controlled expanded state. When provided, the component becomes controlled */
73
+ expanded?: boolean;
74
+ /** Callback fired when the accordion's expanded state changes */
75
+ onExpandedChange?: (expanded: boolean) => void;
76
+ /** Whether the accordion is disabled */
77
+ disabled?: boolean;
78
+ /** Content to display when the accordion is expanded (Figma Slot: 'content') */
79
+ children?: React.ReactNode;
80
+ /** Modes object passed to getVariableByName for all design tokens */
81
+ modes?: Record<string, any>;
82
+ /** Optional container style overrides */
83
+ style?: StyleProp<ViewStyle>;
84
+ /** Accessibility label for screen readers. If not provided, uses title */
85
+ accessibilityLabel?: string;
86
+ /** Additional accessibility hint for screen readers */
87
+ accessibilityHint?: string;
88
+ /** Additional accessibility state information */
89
+ accessibilityState?: AccessibilityState;
90
+ /** Web-specific accessibility props (only used on web platform) */
91
+ webAccessibilityProps?: WebAccessibilityProps;
92
+ } & React.ComponentProps<typeof View>;
93
+
94
+ /**
95
+ * Accordion component that mirrors the Figma "Accordion" component.
96
+ *
97
+ * This component supports:
98
+ * - **Expandable/collapsible content** with smooth animation
99
+ * - **States**: Idle, Hover, Open, Disabled
100
+ * - **Slot** for custom content
101
+ * - **Design-token driven styling** via `getVariableByName` and `modes`
102
+ *
103
+ * Wherever the Figma layer name contains "Slot", this component exposes a
104
+ * dedicated React "slot" prop:
105
+ * - Slot "content" → `children`
106
+ *
107
+ * @component
108
+ * @param {Object} props
109
+ * @param {string} [props.title='Accordion title'] - The accordion header title
110
+ * @param {boolean} [props.defaultExpanded=false] - Initial expanded state
111
+ * @param {boolean} [props.expanded] - Controlled expanded state
112
+ * @param {Function} [props.onExpandedChange] - Callback fired when expanded state changes
113
+ * @param {boolean} [props.disabled=false] - Whether the accordion is disabled
114
+ * @param {React.ReactNode} [props.children] - Content to display when expanded
115
+ * @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
116
+ * @param {Object} [props.style] - Optional container style overrides
117
+ * @param {string} [props.accessibilityLabel] - Accessibility label for the accordion. If not provided, uses title
118
+ * @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
119
+ */
120
+ function Accordion({
121
+ title = 'Accordion title',
122
+ defaultExpanded = false,
123
+ expanded: controlledExpanded,
124
+ onExpandedChange,
125
+ disabled = false,
126
+ children,
127
+ modes = {},
128
+ style,
129
+ accessibilityLabel,
130
+ accessibilityHint,
131
+ accessibilityState,
132
+ webAccessibilityProps,
133
+ ...rest
134
+ }: AccordionProps) {
135
+ // Internal state for uncontrolled mode
136
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded)
137
+
138
+ // Determine if controlled or uncontrolled
139
+ const isControlled = controlledExpanded !== undefined
140
+ const isExpanded = isControlled ? controlledExpanded : internalExpanded
141
+
142
+ // Hover state for web
143
+ const [isHovered, setIsHovered] = useState(false)
144
+
145
+ // Handle toggle
146
+ const handleToggle = () => {
147
+ if (disabled) return
148
+
149
+ // Animate the layout change
150
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
151
+
152
+ if (isControlled) {
153
+ onExpandedChange?.(!isExpanded)
154
+ } else {
155
+ setInternalExpanded(!isExpanded)
156
+ onExpandedChange?.(!isExpanded)
157
+ }
158
+ }
159
+
160
+ // Resolve design tokens
161
+ const titleColor = disabled
162
+ ? '#999999'
163
+ : getVariableByName('accordion/title/color', modes) || '#0d0d0d'
164
+ const titleFontSize = getVariableByName('accordion/title/fontSize', modes) || 18
165
+ const titleLineHeight = getVariableByName('accordion/title/lineHeight', modes) || 20
166
+ const titleFontFamily = getVariableByName('accordion/title/fontFamily', modes) || 'System'
167
+
168
+ const iconColor = getVariableByName('accordion/icon/color', modes) || '#141414'
169
+ const iconSize = getVariableByName('accordion/icon/size', modes) || 24
170
+
171
+ const headerGap = getVariableByName('accordion/header/gap', modes) || 12
172
+ const headerPaddingVertical = getVariableByName('accordion/header/padding/vertical', modes) || 24
173
+ const headerBackground = isHovered && !disabled
174
+ ? '#f2f2f2'
175
+ : getVariableByName('accordion/header/background', modes) || 'transparent'
176
+
177
+ const contentGap = getVariableByName('accordion/content/gap', modes) || 12
178
+ const contentPaddingTop = getVariableByName('accordion/content/padding/top', modes) || 8
179
+ const contentPaddingBottom = isExpanded
180
+ ? (getVariableByName('accordion/content/padding/bottom', modes) ?? 24)
181
+ : 8
182
+
183
+ const borderColor = getVariableByName('accordion/border/color', modes) || '#e6e6e6'
184
+
185
+ // Styles
186
+ const containerStyle: ViewStyle = {
187
+ borderBottomWidth: 1,
188
+ borderBottomColor: borderColor,
189
+ }
190
+
191
+ const headerStyle: ViewStyle = {
192
+ flexDirection: 'row',
193
+ alignItems: 'center',
194
+ gap: headerGap,
195
+ paddingVertical: headerPaddingVertical,
196
+ paddingHorizontal: 0,
197
+ backgroundColor: headerBackground,
198
+ overflow: 'hidden',
199
+ }
200
+
201
+ const titleStyle: TextStyle = {
202
+ flex: 1,
203
+ color: titleColor,
204
+ fontSize: titleFontSize,
205
+ lineHeight: titleLineHeight,
206
+ fontFamily: titleFontFamily,
207
+ fontWeight: '700',
208
+ }
209
+
210
+ const contentStyle: ViewStyle = {
211
+ backgroundColor: 'transparent',
212
+ gap: contentGap,
213
+ paddingTop: contentPaddingTop,
214
+ paddingBottom: contentPaddingBottom,
215
+ paddingHorizontal: 0,
216
+ overflow: 'hidden',
217
+ }
218
+
219
+ // Generate default accessibility label
220
+ const defaultAccessibilityLabel = accessibilityLabel || title
221
+
222
+ // Web platform support
223
+ const webProps = usePressableWebSupport({
224
+ restProps: {},
225
+ onPress: handleToggle,
226
+ disabled,
227
+ accessibilityLabel: defaultAccessibilityLabel,
228
+ webAccessibilityProps,
229
+ })
230
+
231
+ // Process children to pass modes
232
+ const processedChildren = children
233
+ ? cloneChildrenWithModes(React.Children.toArray(children), modes)
234
+ : null
235
+
236
+ return (
237
+ <View style={[containerStyle, style]} {...rest}>
238
+ <Pressable
239
+ accessibilityRole="button"
240
+ accessibilityLabel={defaultAccessibilityLabel}
241
+ accessibilityHint={accessibilityHint || (isExpanded ? 'Collapse accordion' : 'Expand accordion')}
242
+ accessibilityState={{
243
+ expanded: isExpanded,
244
+ disabled,
245
+ ...accessibilityState,
246
+ }}
247
+ onPress={handleToggle}
248
+ disabled={disabled}
249
+ onHoverIn={() => setIsHovered(true)}
250
+ onHoverOut={() => setIsHovered(false)}
251
+ style={({ pressed }) => [
252
+ headerStyle,
253
+ pressed && !disabled ? { opacity: 0.9 } : null,
254
+ ]}
255
+ {...webProps}
256
+ >
257
+ <Text style={titleStyle} numberOfLines={1}>
258
+ {title}
259
+ </Text>
260
+ <Icon
261
+ name={isExpanded ? 'ic_minus' : 'ic_add'}
262
+ size={iconSize}
263
+ color={disabled ? '#999999' : iconColor}
264
+ accessibilityElementsHidden={true}
265
+ importantForAccessibility="no"
266
+ />
267
+ </Pressable>
268
+
269
+ {isExpanded && processedChildren && (
270
+ <View style={contentStyle}>
271
+ {processedChildren}
272
+ </View>
273
+ )}
274
+ </View>
275
+ )
276
+ }
277
+
278
+ export default Accordion
279
+
@@ -99,3 +99,4 @@ All tokens support mode-based theming through the `modes` prop.
99
99
 
100
100
  The ActionFooter uses `accessibilityRole="toolbar"` to indicate it contains a group of action controls. Provide a meaningful `accessibilityLabel` to describe the footer's purpose to screen reader users.
101
101
 
102
+
@@ -0,0 +1,54 @@
1
+ import { Meta, Story, Canvas, PureArgsTable as Controls } from '@storybook/addon-docs/blocks';
2
+ import * as ActionTileStories from './ActionTile.stories';
3
+
4
+ <Meta of={ActionTileStories} />
5
+
6
+ # ActionTile
7
+
8
+ A tile component used for dashboard actions, displaying an icon and a label.
9
+
10
+
11
+ ## Available Collections and Modes
12
+
13
+ This component uses the following design token collections. Each collection supports multiple modes that can be configured via the `modes` prop.
14
+
15
+ ### Color Mode
16
+ - **Modes:** Light | Dark
17
+ - **Default:** Light
18
+ ## Usage
19
+
20
+ ```tsx
21
+ import ActionTile from './ActionTile';
22
+ import IconCapsule from '../IconCapsule/IconCapsule';
23
+
24
+ // Default usage
25
+ <ActionTile label="Cards" />
26
+
27
+ // With custom icon
28
+ <ActionTile
29
+ label="Offers"
30
+ icon={<IconCapsule iconName="ic_offers" />}
31
+ />
32
+ ```
33
+
34
+ <Canvas of={ActionTileStories.Default} />
35
+
36
+ <Controls />
37
+
38
+
39
+ ## Design Tokens
40
+
41
+ This component uses the following design tokens, resolved through `getVariableByName`:
42
+
43
+ - **`actionTile/background`**
44
+ - **`actionTile/fontFamily`**
45
+ - **`actionTile/fontSize`**
46
+ - **`actionTile/fontWeight`**
47
+ - **`actionTile/foreground`**
48
+ - **`actionTile/gap`**
49
+ - **`actionTile/lineHeight`**
50
+ - **`actionTile/padding/horizontal`**
51
+ - **`actionTile/padding/vertical`**
52
+ - **`actionTile/radius`**
53
+
54
+ All tokens support mode-based theming through the `modes` prop.