@seed-design/lynx-react 0.2.0-alpha.2 → 0.2.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.
Files changed (186) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +26 -32
  3. package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
  4. package/lib/components/ActionButton/ActionButton.jsx +139 -120
  5. package/lib/components/ActionButton/index.d.ts +1 -1
  6. package/lib/components/AppBar/AppBar.d.ts +35 -0
  7. package/lib/components/AppBar/AppBar.d.ts.map +1 -0
  8. package/lib/components/AppBar/AppBar.jsx +132 -0
  9. package/lib/components/AppBar/AppBar.namespace.d.ts +2 -0
  10. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -0
  11. package/lib/components/AppBar/AppBar.namespace.js +15 -0
  12. package/lib/components/AppBar/context.d.ts +13 -0
  13. package/lib/components/AppBar/context.d.ts.map +1 -0
  14. package/lib/components/AppBar/context.js +12 -0
  15. package/lib/components/AppBar/index.d.ts +3 -0
  16. package/lib/components/AppBar/index.d.ts.map +1 -0
  17. package/lib/components/AppBar/useAppBar.d.ts +15 -0
  18. package/lib/components/AppBar/useAppBar.d.ts.map +1 -0
  19. package/lib/components/AppBar/useAppBar.js +72 -0
  20. package/lib/components/Badge/Badge.d.ts +14 -0
  21. package/lib/components/Badge/Badge.d.ts.map +1 -0
  22. package/lib/components/Badge/Badge.jsx +15 -0
  23. package/lib/components/Badge/index.d.ts +2 -0
  24. package/lib/components/Badge/index.d.ts.map +1 -0
  25. package/lib/components/BottomSheet/BottomSheet.d.ts +22 -27
  26. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/lib/components/BottomSheet/BottomSheet.jsx +127 -142
  28. package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +1 -1
  29. package/lib/components/BottomSheet/BottomSheet.namespace.js +18 -1
  30. package/lib/components/BottomSheet/index.d.ts +2 -2
  31. package/lib/components/Box/Box.d.ts +20 -0
  32. package/lib/components/Box/Box.d.ts.map +1 -0
  33. package/lib/components/Box/Box.jsx +14 -0
  34. package/lib/components/Box/index.d.ts +2 -0
  35. package/lib/components/Box/index.d.ts.map +1 -0
  36. package/lib/components/Checkbox/Checkbox.d.ts +14 -30
  37. package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
  38. package/lib/components/Checkbox/Checkbox.jsx +107 -98
  39. package/lib/components/Checkbox/Checkbox.namespace.d.ts +1 -1
  40. package/lib/components/Checkbox/Checkbox.namespace.js +12 -1
  41. package/lib/components/Checkbox/index.d.ts +2 -2
  42. package/lib/components/Icon/Icon.d.ts +37 -0
  43. package/lib/components/Icon/Icon.d.ts.map +1 -0
  44. package/lib/components/Icon/Icon.jsx +143 -0
  45. package/lib/components/Icon/index.d.ts +2 -0
  46. package/lib/components/Icon/index.d.ts.map +1 -0
  47. package/lib/components/ProgressCircle/ProgressCircle.d.ts +4 -8
  48. package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
  49. package/lib/components/ProgressCircle/ProgressCircle.jsx +255 -260
  50. package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts +1 -1
  51. package/lib/components/ProgressCircle/ProgressCircle.namespace.js +9 -1
  52. package/lib/components/ProgressCircle/index.d.ts +2 -2
  53. package/lib/components/RadioGroup/RadioGroup.d.ts +12 -28
  54. package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  55. package/lib/components/RadioGroup/RadioGroup.jsx +134 -118
  56. package/lib/components/RadioGroup/RadioGroup.namespace.d.ts +1 -1
  57. package/lib/components/RadioGroup/RadioGroup.namespace.js +12 -1
  58. package/lib/components/RadioGroup/index.d.ts +2 -2
  59. package/lib/components/Stack/Stack.d.ts +33 -0
  60. package/lib/components/Stack/Stack.d.ts.map +1 -0
  61. package/lib/components/Stack/Stack.jsx +39 -0
  62. package/lib/components/Stack/index.d.ts +2 -0
  63. package/lib/components/Stack/index.d.ts.map +1 -0
  64. package/lib/components/Switch/Switch.d.ts +8 -14
  65. package/lib/components/Switch/Switch.d.ts.map +1 -1
  66. package/lib/components/Switch/Switch.jsx +82 -56
  67. package/lib/components/Switch/Switch.namespace.d.ts +1 -1
  68. package/lib/components/Switch/Switch.namespace.js +11 -1
  69. package/lib/components/Switch/index.d.ts +2 -2
  70. package/lib/components/TagGroup/TagGroup.d.ts +8 -14
  71. package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
  72. package/lib/components/TagGroup/TagGroup.jsx +42 -39
  73. package/lib/components/TagGroup/TagGroup.namespace.d.ts +1 -1
  74. package/lib/components/TagGroup/TagGroup.namespace.js +10 -1
  75. package/lib/components/TagGroup/index.d.ts +2 -2
  76. package/lib/components/Text/Text.d.ts +7 -0
  77. package/lib/components/Text/Text.d.ts.map +1 -0
  78. package/lib/components/Text/Text.jsx +28 -0
  79. package/lib/components/Text/index.d.ts +2 -0
  80. package/lib/components/Text/index.d.ts.map +1 -0
  81. package/lib/components/index.d.ts +13 -7
  82. package/lib/components/index.d.ts.map +1 -1
  83. package/lib/hooks/index.d.ts +4 -3
  84. package/lib/hooks/index.d.ts.map +1 -1
  85. package/lib/hooks/{use-controllable-state.d.ts → useControllableState.d.ts} +1 -1
  86. package/lib/hooks/useControllableState.d.ts.map +1 -0
  87. package/lib/hooks/useControllableState.js +27 -0
  88. package/lib/hooks/useIconColor.d.ts +19 -0
  89. package/lib/hooks/useIconColor.d.ts.map +1 -0
  90. package/lib/hooks/useIconColor.js +38 -0
  91. package/lib/hooks/{use-press-tap.d.ts → usePressTap.d.ts} +12 -9
  92. package/lib/hooks/usePressTap.d.ts.map +1 -0
  93. package/lib/hooks/usePressTap.js +35 -0
  94. package/lib/hooks/useSafeArea.d.ts +19 -0
  95. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  96. package/lib/hooks/useSafeArea.js +33 -0
  97. package/lib/index.d.ts +4 -3
  98. package/lib/index.d.ts.map +1 -1
  99. package/lib/index.js +29 -3
  100. package/lib/types.d.ts +51 -0
  101. package/lib/types.d.ts.map +1 -0
  102. package/lib/utils/children.d.ts +3 -0
  103. package/lib/utils/children.d.ts.map +1 -0
  104. package/lib/utils/children.js +8 -0
  105. package/lib/utils/create-slot-recipe-context.d.ts +13 -12
  106. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  107. package/lib/utils/create-slot-recipe-context.jsx +88 -70
  108. package/lib/utils/get-seed-class-name.d.ts +6 -7
  109. package/lib/utils/get-seed-class-name.d.ts.map +1 -1
  110. package/lib/utils/get-seed-class-name.js +26 -37
  111. package/lib/utils/index.d.ts +4 -4
  112. package/lib/utils/resolve-recipe-token.js +45 -43
  113. package/lib/utils/split-multiple-variants-props.js +32 -32
  114. package/lib/utils/styled.d.ts +83 -0
  115. package/lib/utils/styled.d.ts.map +1 -0
  116. package/lib/utils/styled.js +144 -0
  117. package/package.json +9 -9
  118. package/src/components/ActionButton/ActionButton.test.tsx +167 -0
  119. package/src/components/ActionButton/ActionButton.tsx +210 -168
  120. package/src/components/AppBar/AppBar.namespace.ts +18 -0
  121. package/src/components/AppBar/AppBar.test.tsx +270 -0
  122. package/src/components/AppBar/AppBar.tsx +288 -0
  123. package/src/components/AppBar/context.ts +27 -0
  124. package/src/components/AppBar/index.ts +21 -0
  125. package/src/components/AppBar/useAppBar.ts +100 -0
  126. package/src/components/Badge/Badge.test.tsx +54 -0
  127. package/src/components/Badge/Badge.tsx +32 -0
  128. package/src/components/Badge/index.ts +1 -0
  129. package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
  130. package/src/components/BottomSheet/BottomSheet.tsx +120 -83
  131. package/src/components/Box/Box.test.tsx +85 -0
  132. package/src/components/Box/Box.tsx +39 -0
  133. package/src/components/Box/index.ts +1 -0
  134. package/src/components/Checkbox/Checkbox.tsx +108 -117
  135. package/src/components/Icon/Icon.tsx +265 -0
  136. package/src/components/Icon/index.ts +8 -0
  137. package/src/components/ProgressCircle/ProgressCircle.tsx +39 -25
  138. package/src/components/RadioGroup/RadioGroup.tsx +100 -118
  139. package/src/components/Stack/Stack.tsx +97 -0
  140. package/src/components/Stack/index.ts +1 -0
  141. package/src/components/Switch/Switch.tsx +61 -52
  142. package/src/components/TagGroup/TagGroup.tsx +27 -25
  143. package/src/components/Text/Text.tsx +64 -0
  144. package/src/components/Text/index.ts +1 -0
  145. package/src/components/index.ts +6 -0
  146. package/src/hooks/index.ts +4 -3
  147. package/src/hooks/{__tests__/use-controllable-state.test.ts → useControllableState.test.ts} +1 -1
  148. package/src/hooks/useIconColor.test.ts +13 -0
  149. package/src/hooks/useIconColor.ts +67 -0
  150. package/src/hooks/{__tests__/use-press-tap.test.ts → usePressTap.test.ts} +1 -1
  151. package/src/hooks/{use-press-tap.ts → usePressTap.ts} +14 -10
  152. package/src/hooks/useSafeArea.test.ts +83 -0
  153. package/src/hooks/useSafeArea.ts +56 -0
  154. package/src/index.ts +1 -0
  155. package/src/types.ts +76 -0
  156. package/src/utils/children.ts +13 -0
  157. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  158. package/src/utils/create-slot-recipe-context.tsx +55 -15
  159. package/src/utils/get-seed-class-name.ts +7 -12
  160. package/src/utils/{__tests__/split-multiple-variants-props.test.ts → split-multiple-variants-props.test.ts} +1 -1
  161. package/src/utils/styled.ts +442 -0
  162. package/lib/components/ActionButton/index.js +0 -1
  163. package/lib/components/BottomSheet/index.js +0 -2
  164. package/lib/components/Checkbox/index.js +0 -2
  165. package/lib/components/ProgressCircle/index.js +0 -2
  166. package/lib/components/RadioGroup/index.js +0 -2
  167. package/lib/components/Switch/index.js +0 -2
  168. package/lib/components/TagGroup/index.js +0 -2
  169. package/lib/components/index.js +0 -7
  170. package/lib/hooks/index.js +0 -3
  171. package/lib/hooks/use-controllable-state.d.ts.map +0 -1
  172. package/lib/hooks/use-controllable-state.js +0 -27
  173. package/lib/hooks/use-icon-color.d.ts +0 -15
  174. package/lib/hooks/use-icon-color.d.ts.map +0 -1
  175. package/lib/hooks/use-icon-color.js +0 -37
  176. package/lib/hooks/use-press-tap.d.ts.map +0 -1
  177. package/lib/hooks/use-press-tap.js +0 -35
  178. package/lib/utils/index.js +0 -4
  179. package/src/components/ActionButton/__tests__/ActionButton.test.tsx +0 -35
  180. package/src/components/BottomSheet/__tests__/BottomSheet.test.tsx +0 -119
  181. package/src/components/__tests__/ProgressCircle.test.ts +0 -33
  182. package/src/components/__tests__/exports.test.ts +0 -38
  183. package/src/components/__tests__/switch.test.ts +0 -34
  184. package/src/hooks/use-icon-color.ts +0 -51
  185. package/src/utils/__tests__/create-slot-recipe-context.test.tsx +0 -228
  186. /package/src/hooks/{use-controllable-state.ts → useControllableState.ts} +0 -0
@@ -0,0 +1,270 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ act,
4
+ fireEvent,
5
+ getQueriesForElement,
6
+ render,
7
+ waitFor,
8
+ } from "@lynx-js/react/testing-library";
9
+ import { afterEach, describe, expect, it, vi } from "vitest";
10
+
11
+ import * as AppBar from "./AppBar.namespace";
12
+
13
+ type TestSystemInfo = { platform?: string };
14
+
15
+ interface TestLynxGlobal {
16
+ SystemInfo?: TestSystemInfo;
17
+ lynx?: {
18
+ __globalProps?: {
19
+ safeAreaInsetTop?: number;
20
+ safeAreaInsetBottom?: number;
21
+ };
22
+ };
23
+ lynxTestingEnv?: {
24
+ backgroundThread: {
25
+ globalThis: TestLynxGlobal;
26
+ };
27
+ mainThread: {
28
+ globalThis: TestLynxGlobal;
29
+ };
30
+ };
31
+ }
32
+
33
+ function getRenderedQueries() {
34
+ return getQueriesForElement(getAppBarRoot());
35
+ }
36
+
37
+ function getRenderedRoot() {
38
+ const root = elementTree.root;
39
+
40
+ if (!root) {
41
+ throw new Error("Expected Lynx render root to exist.");
42
+ }
43
+
44
+ return root;
45
+ }
46
+
47
+ function getAppBarRoot() {
48
+ const root = getRenderedRoot();
49
+
50
+ if (root.classList.contains("seed-app-bar__root")) return root;
51
+
52
+ const appBarRoot = root.querySelector<HTMLElement>(".seed-app-bar__root");
53
+ if (!appBarRoot) {
54
+ throw new Error("Expected AppBar root to exist.");
55
+ }
56
+
57
+ return appBarRoot;
58
+ }
59
+
60
+ function expectStyle(style: CSSStyleDeclaration, expected: Record<string, string>) {
61
+ for (const [key, value] of Object.entries(expected)) {
62
+ expect(style.getPropertyValue(key)).toBe(value);
63
+ }
64
+ }
65
+
66
+ function setSystemInfo(systemInfo: TestSystemInfo | undefined) {
67
+ const lynxTestingEnv = (globalThis as TestLynxGlobal).lynxTestingEnv;
68
+ const globals = [
69
+ globalThis as TestLynxGlobal,
70
+ lynxTestingEnv?.backgroundThread.globalThis,
71
+ lynxTestingEnv?.mainThread.globalThis,
72
+ ].filter((global): global is TestLynxGlobal => Boolean(global));
73
+
74
+ for (const global of globals) {
75
+ if (systemInfo == null) {
76
+ delete global.SystemInfo;
77
+ } else {
78
+ global.SystemInfo = systemInfo;
79
+ }
80
+ }
81
+ }
82
+
83
+ function setGlobalProps(
84
+ globalProps: NonNullable<NonNullable<TestLynxGlobal["lynx"]>["__globalProps"]>,
85
+ ) {
86
+ const lynxTestingEnv = (globalThis as TestLynxGlobal).lynxTestingEnv;
87
+ const globals = [
88
+ globalThis as TestLynxGlobal,
89
+ lynxTestingEnv?.backgroundThread.globalThis,
90
+ lynxTestingEnv?.mainThread.globalThis,
91
+ ].filter((global): global is TestLynxGlobal => Boolean(global));
92
+
93
+ for (const global of globals) {
94
+ global.lynx = {
95
+ ...global.lynx,
96
+ __globalProps: globalProps,
97
+ };
98
+ }
99
+ }
100
+
101
+ function stubPlatform(platform: string | undefined) {
102
+ setSystemInfo(platform == null ? {} : { platform });
103
+ }
104
+
105
+ describe("AppBar", () => {
106
+ afterEach(() => {
107
+ setSystemInfo({ platform: "iOS" });
108
+ setGlobalProps({});
109
+ vi.unstubAllGlobals();
110
+ });
111
+
112
+ it("uses android theme by default on Android", () => {
113
+ stubPlatform("Android");
114
+
115
+ render(<AppBar.Root />);
116
+
117
+ expect(getAppBarRoot()).toHaveClass("seed-app-bar__root--theme_android");
118
+ });
119
+
120
+ it("uses cupertino theme by default outside Android", () => {
121
+ stubPlatform("iOS");
122
+
123
+ render(<AppBar.Root />);
124
+
125
+ expect(getAppBarRoot()).toHaveClass("seed-app-bar__root--theme_cupertino");
126
+ });
127
+
128
+ it("uses cupertino theme when SystemInfo.platform is unavailable", () => {
129
+ stubPlatform(undefined);
130
+
131
+ render(<AppBar.Root />);
132
+
133
+ expect(getAppBarRoot()).toHaveClass("seed-app-bar__root--theme_cupertino");
134
+ });
135
+
136
+ it("lets explicit theme override the platform default", () => {
137
+ stubPlatform("Android");
138
+
139
+ render(<AppBar.Root theme="cupertino" />);
140
+
141
+ expect(getAppBarRoot()).toHaveClass("seed-app-bar__root--theme_cupertino");
142
+ expect(getAppBarRoot()).not.toHaveClass("seed-app-bar__root--theme_android");
143
+ });
144
+
145
+ it("applies safe area from useSafeArea to the root layout", () => {
146
+ setGlobalProps({ safeAreaInsetTop: 47 });
147
+
148
+ render(<AppBar.Root theme="cupertino" />);
149
+
150
+ expectStyle(getAppBarRoot().style, {
151
+ height: "calc(91px)",
152
+ "padding-top": "47px",
153
+ });
154
+ });
155
+
156
+ it("does not read the safe area CSS variable override as the JS layout source", () => {
157
+ setGlobalProps({ safeAreaInsetTop: 47 });
158
+
159
+ render(
160
+ <AppBar.Root
161
+ theme="cupertino"
162
+ style={{ "--seed-safe-area-top": "0px" } as AppBar.RootProps["style"]}
163
+ />,
164
+ );
165
+
166
+ expectStyle(getAppBarRoot().style, {
167
+ height: "calc(91px)",
168
+ "padding-top": "47px",
169
+ });
170
+ });
171
+
172
+ it("keeps Cupertino title-only main centered in the safe-area adjusted bar", () => {
173
+ setGlobalProps({ safeAreaInsetTop: 47 });
174
+
175
+ render(
176
+ <AppBar.Root theme="cupertino">
177
+ <AppBar.Main>
178
+ <AppBar.Title>Title</AppBar.Title>
179
+ </AppBar.Main>
180
+ </AppBar.Root>,
181
+ );
182
+
183
+ const main = getAppBarRoot().querySelector<HTMLElement>(".seed-app-bar-main__root");
184
+
185
+ expect(main).toBeInTheDocument();
186
+ expectStyle(main!.style, {
187
+ top: "47px",
188
+ bottom: "0px",
189
+ });
190
+ });
191
+
192
+ it("renders slots with recipe class names", () => {
193
+ render(
194
+ <AppBar.Root>
195
+ <AppBar.Left>
196
+ <AppBar.IconButton accessibility-label="Back" />
197
+ </AppBar.Left>
198
+ <AppBar.Main layout="withSubtitle">
199
+ <AppBar.Title>Title</AppBar.Title>
200
+ <AppBar.Subtitle>Subtitle</AppBar.Subtitle>
201
+ </AppBar.Main>
202
+ <AppBar.Right>
203
+ <AppBar.Slot>
204
+ <text>Done</text>
205
+ </AppBar.Slot>
206
+ </AppBar.Right>
207
+ </AppBar.Root>,
208
+ );
209
+
210
+ const { getByText } = getRenderedQueries();
211
+ const root = getAppBarRoot();
212
+
213
+ expect(root.querySelector(".seed-app-bar__left")).toBeInTheDocument();
214
+ expect(root.querySelector(".seed-app-bar__right")).toBeInTheDocument();
215
+ expect(root.querySelector(".seed-app-bar__iconButton")).toBeInTheDocument();
216
+ expect(root.querySelector(".seed-app-bar__custom")).toBeInTheDocument();
217
+ expect(getByText("Title")).toHaveClass("seed-app-bar-main__title");
218
+ expect(getByText("Subtitle")).toHaveClass("seed-app-bar-main__subtitle");
219
+ });
220
+
221
+ it("maps icon button labels to Lynx accessibility props", () => {
222
+ render(
223
+ <AppBar.Root>
224
+ <AppBar.Left>
225
+ <AppBar.IconButton accessibility-label="Back" />
226
+ </AppBar.Left>
227
+ </AppBar.Root>,
228
+ );
229
+
230
+ const iconButton = getAppBarRoot().querySelector(".seed-app-bar__iconButton");
231
+
232
+ expect(iconButton).toHaveAttribute("accessibility-label", "Back");
233
+ expect(iconButton).toHaveAttribute("accessibility-element", "true");
234
+ expect(iconButton).toHaveAttribute("accessibility-traits", "button");
235
+ });
236
+
237
+ it("updates centered title padding from left and right layout widths", async () => {
238
+ render(
239
+ <AppBar.Root>
240
+ <AppBar.Left>
241
+ <text>Left</text>
242
+ </AppBar.Left>
243
+ <AppBar.Main>
244
+ <AppBar.Title>Title</AppBar.Title>
245
+ </AppBar.Main>
246
+ <AppBar.Right>
247
+ <text>Right</text>
248
+ </AppBar.Right>
249
+ </AppBar.Root>,
250
+ );
251
+
252
+ const root = getAppBarRoot();
253
+ const main = root.querySelector(".seed-app-bar-main__root");
254
+ const left = root.querySelector(".seed-app-bar__left");
255
+ const right = root.querySelector(".seed-app-bar__right");
256
+
257
+ if (!main || !left || !right) {
258
+ throw new Error("Expected AppBar main, left, and right slots to exist.");
259
+ }
260
+
261
+ act(() => {
262
+ fireEvent.layoutchange(left, { width: 48 });
263
+ fireEvent.layoutchange(right, { width: 72 });
264
+ });
265
+
266
+ await waitFor(() => {
267
+ expect(main).toHaveStyle({ paddingLeft: "72px", paddingRight: "72px" });
268
+ });
269
+ });
270
+ });
@@ -0,0 +1,288 @@
1
+ import { appBar, type AppBarVariantProps } from "@seed-design/lynx-css/recipes/app-bar";
2
+ import {
3
+ appBarMain,
4
+ type AppBarMainVariantProps,
5
+ } from "@seed-design/lynx-css/recipes/app-bar-main";
6
+ import * as React from "@lynx-js/react";
7
+ import clsx from "clsx";
8
+
9
+ import type {
10
+ LynxElementProps,
11
+ LynxIconElementProps,
12
+ LynxPressableProps,
13
+ LynxStyledElementProps,
14
+ LynxTextRef,
15
+ LynxViewProps,
16
+ LynxViewRef,
17
+ } from "../../types";
18
+ import { createSlotRecipeContext } from "../../utils/create-slot-recipe-context";
19
+ import { Icon } from "../Icon";
20
+ import { AppBarProvider, useAppBarContext } from "./context";
21
+ import { getLayoutWidth, getMainLayoutStyle, useAppBar } from "./useAppBar";
22
+
23
+ type AppBarClassNames = ReturnType<typeof appBar>;
24
+ type AppBarMainClassNames = ReturnType<typeof appBarMain>;
25
+ type LayoutChangeHandler = NonNullable<LynxViewProps["bindlayoutchange"]>;
26
+
27
+ const { ClassNamesProvider: AppBarClassNamesProvider, useClassNames: useAppBarRecipeClassNames } =
28
+ createSlotRecipeContext(appBar);
29
+ const {
30
+ ClassNamesProvider: AppBarMainClassNamesProvider,
31
+ useClassNames: useAppBarMainRecipeClassNames,
32
+ } = createSlotRecipeContext(appBarMain);
33
+
34
+ function useAppBarClassNames(consumer: string): AppBarClassNames {
35
+ try {
36
+ return useAppBarRecipeClassNames();
37
+ } catch {
38
+ throw new Error(`<${consumer}/> must be rendered inside <AppBarRoot/>.`);
39
+ }
40
+ }
41
+
42
+ function useAppBarMainClassNames(consumer: string): AppBarMainClassNames {
43
+ try {
44
+ return useAppBarMainRecipeClassNames();
45
+ } catch {
46
+ throw new Error(`<${consumer}/> must be rendered inside <AppBarMain/>.`);
47
+ }
48
+ }
49
+
50
+ ////////////////////////////////////////////////////////////////////////////////////
51
+
52
+ export interface AppBarRootProps extends AppBarVariantProps, LynxStyledElementProps {}
53
+
54
+ export const AppBarRoot = React.forwardRef<unknown, AppBarRootProps>((props, ref) => {
55
+ const [variantProps, otherProps] = appBar.splitVariantProps(props);
56
+ const { children, className, style, ...nativeProps } = otherProps;
57
+ const { contextValue, resolvedVariantProps, rootLayoutStyle } = useAppBar(variantProps);
58
+ const classNames = appBar(resolvedVariantProps);
59
+
60
+ return (
61
+ <AppBarProvider value={contextValue}>
62
+ <AppBarClassNamesProvider value={classNames}>
63
+ <view
64
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
65
+ {...nativeProps}
66
+ className={clsx(classNames.root, className)}
67
+ style={
68
+ {
69
+ "--seed-safe-area-top": contextValue.safeAreaInsetTop,
70
+ "--centered-title-padding-x": contextValue.centeredTitlePaddingX,
71
+ ...rootLayoutStyle,
72
+ ...style,
73
+ } as LynxViewProps["style"]
74
+ }
75
+ >
76
+ <view accessibility-elements-hidden className={classNames.background} />
77
+ {children}
78
+ </view>
79
+ </AppBarClassNamesProvider>
80
+ </AppBarProvider>
81
+ );
82
+ });
83
+ AppBarRoot.displayName = "AppBarRoot";
84
+
85
+ ////////////////////////////////////////////////////////////////////////////////////
86
+
87
+ export interface AppBarLeftProps extends LynxStyledElementProps {
88
+ bindlayoutchange?: LynxViewProps["bindlayoutchange"];
89
+ }
90
+
91
+ export const AppBarLeft = React.forwardRef<unknown, AppBarLeftProps>((props, ref) => {
92
+ const { children, className, bindlayoutchange, ...nativeProps } = props;
93
+ const { setLeftWidth } = useAppBarContext("AppBarLeft");
94
+ const classNames = useAppBarClassNames("AppBarLeft");
95
+
96
+ const handleLayoutChange = React.useCallback<LayoutChangeHandler>(
97
+ (...args) => {
98
+ bindlayoutchange?.(...args);
99
+ const width = getLayoutWidth(args[0]);
100
+ if (width != null) {
101
+ setLeftWidth(width);
102
+ }
103
+ },
104
+ [bindlayoutchange, setLeftWidth],
105
+ );
106
+
107
+ return (
108
+ <view
109
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
110
+ {...nativeProps}
111
+ bindlayoutchange={handleLayoutChange}
112
+ className={clsx(classNames.left, className)}
113
+ >
114
+ {children}
115
+ </view>
116
+ );
117
+ });
118
+ AppBarLeft.displayName = "AppBarLeft";
119
+
120
+ export interface AppBarRightProps extends LynxStyledElementProps {
121
+ bindlayoutchange?: LynxViewProps["bindlayoutchange"];
122
+ }
123
+
124
+ export const AppBarRight = React.forwardRef<unknown, AppBarRightProps>((props, ref) => {
125
+ const { children, className, bindlayoutchange, ...nativeProps } = props;
126
+ const { setRightWidth } = useAppBarContext("AppBarRight");
127
+ const classNames = useAppBarClassNames("AppBarRight");
128
+
129
+ const handleLayoutChange = React.useCallback<LayoutChangeHandler>(
130
+ (...args) => {
131
+ bindlayoutchange?.(...args);
132
+ const width = getLayoutWidth(args[0]);
133
+ if (width != null) {
134
+ setRightWidth(width);
135
+ }
136
+ },
137
+ [bindlayoutchange, setRightWidth],
138
+ );
139
+
140
+ return (
141
+ <view
142
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
143
+ {...nativeProps}
144
+ bindlayoutchange={handleLayoutChange}
145
+ className={clsx(classNames.right, className)}
146
+ >
147
+ {children}
148
+ </view>
149
+ );
150
+ });
151
+ AppBarRight.displayName = "AppBarRight";
152
+
153
+ ////////////////////////////////////////////////////////////////////////////////////
154
+
155
+ export interface AppBarMainProps extends AppBarMainVariantProps, LynxStyledElementProps {}
156
+
157
+ export const AppBarMain = React.forwardRef<unknown, AppBarMainProps>((props, ref) => {
158
+ const { centeredTitlePaddingX, safeAreaInsetTop, sharedVariantProps } =
159
+ useAppBarContext("AppBarMain");
160
+ const [variantProps, otherProps] = appBarMain.splitVariantProps({
161
+ ...sharedVariantProps,
162
+ ...props,
163
+ });
164
+ const resolvedTheme = variantProps.theme ?? "cupertino";
165
+ const classNames = appBarMain(variantProps);
166
+ const { children, className, style, ...nativeProps } = otherProps;
167
+ const centeredTitleStyle =
168
+ variantProps.theme === "cupertino"
169
+ ? {
170
+ paddingLeft: centeredTitlePaddingX,
171
+ paddingRight: centeredTitlePaddingX,
172
+ }
173
+ : undefined;
174
+
175
+ return (
176
+ <AppBarMainClassNamesProvider value={classNames}>
177
+ <view
178
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
179
+ {...nativeProps}
180
+ className={clsx(classNames.root, className)}
181
+ style={
182
+ {
183
+ ...centeredTitleStyle,
184
+ ...getMainLayoutStyle(resolvedTheme, safeAreaInsetTop),
185
+ ...style,
186
+ } as LynxViewProps["style"]
187
+ }
188
+ >
189
+ {children}
190
+ </view>
191
+ </AppBarMainClassNamesProvider>
192
+ );
193
+ });
194
+ AppBarMain.displayName = "AppBarMain";
195
+
196
+ export interface AppBarTitleProps extends LynxElementProps {}
197
+
198
+ export const AppBarTitle = React.forwardRef<unknown, AppBarTitleProps>((props, ref) => {
199
+ const { children, className, ...nativeProps } = props;
200
+ const classNames = useAppBarMainClassNames("AppBarTitle");
201
+
202
+ return (
203
+ <text
204
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
205
+ {...nativeProps}
206
+ className={clsx(classNames.title, className)}
207
+ >
208
+ {children}
209
+ </text>
210
+ );
211
+ });
212
+ AppBarTitle.displayName = "AppBarTitle";
213
+
214
+ export interface AppBarSubtitleProps extends LynxElementProps {}
215
+
216
+ export const AppBarSubtitle = React.forwardRef<unknown, AppBarSubtitleProps>((props, ref) => {
217
+ const { children, className, ...nativeProps } = props;
218
+ const classNames = useAppBarMainClassNames("AppBarSubtitle");
219
+
220
+ return (
221
+ <text
222
+ {...(ref ? { ref: ref as LynxTextRef } : {})}
223
+ {...nativeProps}
224
+ className={clsx(classNames.subtitle, className)}
225
+ >
226
+ {children}
227
+ </text>
228
+ );
229
+ });
230
+ AppBarSubtitle.displayName = "AppBarSubtitle";
231
+
232
+ ////////////////////////////////////////////////////////////////////////////////////
233
+
234
+ export interface AppBarIconButtonProps extends LynxElementProps, LynxPressableProps {
235
+ icon?: React.ReactElement<LynxIconElementProps>;
236
+ "accessibility-label"?: LynxViewProps["accessibility-label"];
237
+ "accessibility-element"?: LynxViewProps["accessibility-element"];
238
+ "accessibility-traits"?: LynxViewProps["accessibility-traits"];
239
+ }
240
+
241
+ export const AppBarIconButton = React.forwardRef<unknown, AppBarIconButtonProps>((props, ref) => {
242
+ const {
243
+ children,
244
+ className,
245
+ icon,
246
+ "accessibility-element": accessibilityElement = true,
247
+ "accessibility-label": accessibilityLabel,
248
+ "accessibility-traits": accessibilityTraits = "button",
249
+ ...nativeProps
250
+ } = props;
251
+ const classNames = useAppBarClassNames("AppBarIconButton");
252
+
253
+ if (process.env.NODE_ENV !== "production" && accessibilityElement && !accessibilityLabel) {
254
+ console.warn("AppBarIconButton requires `accessibility-label` for accessibility.");
255
+ }
256
+
257
+ return (
258
+ <view
259
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
260
+ {...nativeProps}
261
+ accessibility-element={accessibilityElement}
262
+ accessibility-label={accessibilityLabel}
263
+ accessibility-traits={accessibilityTraits}
264
+ className={clsx(classNames.iconButton, className)}
265
+ >
266
+ {icon ? <Icon className={classNames.icon} icon={icon} /> : children}
267
+ </view>
268
+ );
269
+ });
270
+ AppBarIconButton.displayName = "AppBarIconButton";
271
+
272
+ export interface AppBarSlotProps extends LynxStyledElementProps {}
273
+
274
+ export const AppBarSlot = React.forwardRef<unknown, AppBarSlotProps>((props, ref) => {
275
+ const { children, className, ...nativeProps } = props;
276
+ const classNames = useAppBarClassNames("AppBarSlot");
277
+
278
+ return (
279
+ <view
280
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
281
+ {...nativeProps}
282
+ className={clsx(classNames.custom, className)}
283
+ >
284
+ {children}
285
+ </view>
286
+ );
287
+ });
288
+ AppBarSlot.displayName = "AppBarSlot";
@@ -0,0 +1,27 @@
1
+ import { type AppBarMainVariantProps } from "@seed-design/lynx-css/recipes/app-bar-main";
2
+ import * as React from "@lynx-js/react";
3
+
4
+ export type SharedAppBarVariantProps = Pick<
5
+ AppBarMainVariantProps,
6
+ "theme" | "tone" | "transitionStyle"
7
+ >;
8
+
9
+ export interface AppBarContextValue {
10
+ centeredTitlePaddingX: string;
11
+ safeAreaInsetTop: string;
12
+ sharedVariantProps: SharedAppBarVariantProps;
13
+ setLeftWidth: (width: number) => void;
14
+ setRightWidth: (width: number) => void;
15
+ }
16
+
17
+ const AppBarContext = React.createContext<AppBarContextValue | null>(null);
18
+
19
+ export const AppBarProvider = AppBarContext.Provider;
20
+
21
+ export function useAppBarContext(consumer: string): AppBarContextValue {
22
+ const ctx = React.useContext(AppBarContext);
23
+ if (!ctx) {
24
+ throw new Error(`<${consumer}/> must be rendered inside <AppBarRoot/>.`);
25
+ }
26
+ return ctx;
27
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ AppBarIconButton,
3
+ AppBarLeft,
4
+ AppBarMain,
5
+ AppBarRight,
6
+ AppBarRoot,
7
+ AppBarSlot,
8
+ AppBarSubtitle,
9
+ AppBarTitle,
10
+ type AppBarIconButtonProps,
11
+ type AppBarLeftProps,
12
+ type AppBarMainProps,
13
+ type AppBarRightProps,
14
+ type AppBarRootProps,
15
+ type AppBarRootProps as AppBarProps,
16
+ type AppBarSlotProps,
17
+ type AppBarSubtitleProps,
18
+ type AppBarTitleProps,
19
+ } from "./AppBar";
20
+
21
+ export * as AppBar from "./AppBar.namespace";