@seed-design/lynx-react 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Accordion/Accordion.jsx +2 -2
- package/lib/components/ActionButton/ActionButton.jsx +1 -1
- package/lib/components/AlertDialog/AlertDialog.d.ts +64 -0
- package/lib/components/AlertDialog/AlertDialog.d.ts.map +1 -0
- package/lib/components/AlertDialog/AlertDialog.jsx +138 -0
- package/lib/components/AlertDialog/AlertDialog.namespace.d.ts +2 -0
- package/lib/components/AlertDialog/AlertDialog.namespace.d.ts.map +1 -0
- package/lib/components/AlertDialog/AlertDialog.namespace.js +17 -0
- package/lib/components/AlertDialog/index.d.ts +3 -0
- package/lib/components/AlertDialog/index.d.ts.map +1 -0
- package/lib/components/AppBar/AppBar.d.ts +14 -1
- package/lib/components/AppBar/AppBar.d.ts.map +1 -1
- package/lib/components/AppBar/AppBar.jsx +42 -16
- package/lib/components/AppBar/AppBar.namespace.d.ts +1 -1
- package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -1
- package/lib/components/AppBar/context.d.ts +2 -1
- package/lib/components/AppBar/context.d.ts.map +1 -1
- package/lib/components/AppBar/index.d.ts +1 -1
- package/lib/components/AppBar/index.d.ts.map +1 -1
- package/lib/components/AppBar/useAppBar.d.ts +2 -1
- package/lib/components/AppBar/useAppBar.d.ts.map +1 -1
- package/lib/components/AppBar/useAppBar.js +17 -15
- package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts +182 -0
- package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts.map +1 -0
- package/lib/components/AttachmentDisplay/AttachmentDisplay.jsx +444 -0
- package/lib/components/AttachmentDisplay/index.d.ts +2 -0
- package/lib/components/AttachmentDisplay/index.d.ts.map +1 -0
- package/lib/components/AttachmentInput/AttachmentInput.d.ts +179 -0
- package/lib/components/AttachmentInput/AttachmentInput.d.ts.map +1 -0
- package/lib/components/AttachmentInput/AttachmentInput.jsx +520 -0
- package/lib/components/AttachmentInput/index.d.ts +2 -0
- package/lib/components/AttachmentInput/index.d.ts.map +1 -0
- package/lib/components/BottomSheet/BottomSheet.d.ts +1 -1
- package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/lib/components/BottomSheet/BottomSheet.jsx +1 -7
- package/lib/components/Box/Box.d.ts +3 -3
- package/lib/components/Box/Box.d.ts.map +1 -1
- package/lib/components/Callout/Callout.jsx +2 -2
- package/lib/components/Checkbox/Checkbox.jsx +2 -2
- package/lib/components/Chip/Chip.jsx +2 -2
- package/lib/components/ChipTabs/ChipTabs.d.ts +41 -0
- package/lib/components/ChipTabs/ChipTabs.d.ts.map +1 -0
- package/lib/components/ChipTabs/ChipTabs.jsx +39 -0
- package/lib/components/ChipTabs/ChipTabs.namespace.d.ts +2 -0
- package/lib/components/ChipTabs/ChipTabs.namespace.d.ts.map +1 -0
- package/lib/components/ChipTabs/ChipTabs.namespace.js +13 -0
- package/lib/components/ChipTabs/index.d.ts +3 -0
- package/lib/components/ChipTabs/index.d.ts.map +1 -0
- package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts +18 -0
- package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts.map +1 -0
- package/lib/components/ContextualFloatingButton/ContextualFloatingButton.jsx +113 -0
- package/lib/components/ContextualFloatingButton/index.d.ts +2 -0
- package/lib/components/ContextualFloatingButton/index.d.ts.map +1 -0
- package/lib/components/Dialog/Dialog.d.ts +87 -0
- package/lib/components/Dialog/Dialog.d.ts.map +1 -0
- package/lib/components/Dialog/Dialog.jsx +147 -0
- package/lib/components/Dialog/Dialog.namespace.d.ts +2 -0
- package/lib/components/Dialog/Dialog.namespace.d.ts.map +1 -0
- package/lib/components/Dialog/Dialog.namespace.js +18 -0
- package/lib/components/Dialog/index.d.ts +3 -0
- package/lib/components/Dialog/index.d.ts.map +1 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.d.ts +23 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.d.ts.map +1 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.jsx +76 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts +2 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts.map +1 -0
- package/lib/components/FloatingActionButton/FloatingActionButton.namespace.js +10 -0
- package/lib/components/FloatingActionButton/index.d.ts +3 -0
- package/lib/components/FloatingActionButton/index.d.ts.map +1 -0
- package/lib/components/HelpBubble/HelpBubble.d.ts +67 -0
- package/lib/components/HelpBubble/HelpBubble.d.ts.map +1 -0
- package/lib/components/HelpBubble/HelpBubble.jsx +600 -0
- package/lib/components/HelpBubble/HelpBubble.namespace.d.ts +2 -0
- package/lib/components/HelpBubble/HelpBubble.namespace.d.ts.map +1 -0
- package/lib/components/HelpBubble/HelpBubble.namespace.js +18 -0
- package/lib/components/HelpBubble/index.d.ts +3 -0
- package/lib/components/HelpBubble/index.d.ts.map +1 -0
- package/lib/components/Icon/Icon.d.ts +1 -0
- package/lib/components/Icon/Icon.d.ts.map +1 -1
- package/lib/components/Icon/Icon.jsx +2 -1
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts +28 -0
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts.map +1 -0
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.jsx +37 -0
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts +2 -0
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts.map +1 -0
- package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.js +9 -0
- package/lib/components/IdentityPlaceholder/identity-placeholder-business.js +4 -0
- package/lib/components/IdentityPlaceholder/identity-placeholder-person.js +4 -0
- package/lib/components/IdentityPlaceholder/index.d.ts +3 -0
- package/lib/components/IdentityPlaceholder/index.d.ts.map +1 -0
- package/lib/components/InputButton/InputButton.d.ts +2 -0
- package/lib/components/InputButton/InputButton.d.ts.map +1 -1
- package/lib/components/InputButton/InputButton.jsx +40 -8
- package/lib/components/List/List.d.ts +1 -1
- package/lib/components/List/List.d.ts.map +1 -1
- package/lib/components/List/List.jsx +26 -12
- package/lib/components/Menu/Menu.d.ts.map +1 -1
- package/lib/components/Menu/Menu.jsx +15 -5
- package/lib/components/PageBanner/PageBanner.d.ts +3 -0
- package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
- package/lib/components/PageBanner/PageBanner.jsx +49 -11
- package/lib/components/QuantityPicker/QuantityPicker.d.ts +53 -0
- package/lib/components/QuantityPicker/QuantityPicker.d.ts.map +1 -0
- package/lib/components/QuantityPicker/QuantityPicker.jsx +282 -0
- package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts +2 -0
- package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts.map +1 -0
- package/lib/components/QuantityPicker/QuantityPicker.namespace.js +11 -0
- package/lib/components/QuantityPicker/index.d.ts +3 -0
- package/lib/components/QuantityPicker/index.d.ts.map +1 -0
- package/lib/components/RadioGroup/RadioGroup.jsx +2 -2
- package/lib/components/ReactionButton/ReactionButton.d.ts +1 -1
- package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -1
- package/lib/components/ReactionButton/ReactionButton.jsx +22 -13
- package/lib/components/ScrollFog/ScrollFog.d.ts +41 -0
- package/lib/components/ScrollFog/ScrollFog.d.ts.map +1 -0
- package/lib/components/ScrollFog/ScrollFog.jsx +65 -0
- package/lib/components/ScrollFog/index.d.ts +2 -0
- package/lib/components/ScrollFog/index.d.ts.map +1 -0
- package/lib/components/SegmentedControl/SegmentedControl.jsx +2 -2
- package/lib/components/Select/Select.d.ts +112 -0
- package/lib/components/Select/Select.d.ts.map +1 -0
- package/lib/components/Select/Select.jsx +885 -0
- package/lib/components/Select/Select.namespace.d.ts +2 -0
- package/lib/components/Select/Select.namespace.d.ts.map +1 -0
- package/lib/components/Select/Select.namespace.js +23 -0
- package/lib/components/Select/index.d.ts +3 -0
- package/lib/components/Select/index.d.ts.map +1 -0
- package/lib/components/Skeleton/Skeleton.d.ts +15 -0
- package/lib/components/Skeleton/Skeleton.d.ts.map +1 -0
- package/lib/components/Skeleton/Skeleton.jsx +18 -0
- package/lib/components/Skeleton/index.d.ts +2 -0
- package/lib/components/Skeleton/index.d.ts.map +1 -0
- package/lib/components/Slider/Slider.d.ts +101 -0
- package/lib/components/Slider/Slider.d.ts.map +1 -0
- package/lib/components/Slider/Slider.jsx +573 -0
- package/lib/components/Slider/Slider.namespace.d.ts +2 -0
- package/lib/components/Slider/Slider.namespace.d.ts.map +1 -0
- package/lib/components/Slider/Slider.namespace.js +19 -0
- package/lib/components/Slider/Slider.utils.d.ts +19 -0
- package/lib/components/Slider/Slider.utils.d.ts.map +1 -0
- package/lib/components/Slider/Slider.utils.js +106 -0
- package/lib/components/Slider/index.d.ts +3 -0
- package/lib/components/Slider/index.d.ts.map +1 -0
- package/lib/components/Stack/Stack.d.ts +7 -9
- package/lib/components/Stack/Stack.d.ts.map +1 -1
- package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -1
- package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +22 -8
- package/lib/components/Switch/Switch.d.ts.map +1 -1
- package/lib/components/Switch/Switch.jsx +5 -3
- package/lib/components/Tabs/Tabs.d.ts +34 -2
- package/lib/components/Tabs/Tabs.d.ts.map +1 -1
- package/lib/components/Tabs/Tabs.jsx +186 -56
- package/lib/components/Tabs/Tabs.utils.d.ts +14 -1
- package/lib/components/Tabs/Tabs.utils.d.ts.map +1 -1
- package/lib/components/Tabs/Tabs.utils.js +24 -3
- package/lib/components/Tabs/index.d.ts +1 -1
- package/lib/components/Tabs/index.d.ts.map +1 -1
- package/lib/components/index.d.ts +14 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/hooks/useControllableState.d.ts +1 -14
- package/lib/hooks/useControllableState.d.ts.map +1 -1
- package/lib/hooks/usePressTap.d.ts +1 -27
- package/lib/hooks/usePressTap.d.ts.map +1 -1
- package/lib/hooks/useSafeArea.d.ts +8 -4
- package/lib/hooks/useSafeArea.d.ts.map +1 -1
- package/lib/hooks/useSafeArea.js +22 -20
- package/lib/index.js +29 -6
- package/lib/{hooks/useControllableState.js → packages/lynx-react-headless/use-controllable-state/lib/index.js} +2 -3
- package/lib/{hooks/usePressTap.js → packages/lynx-react-headless/use-press-tap/lib/index.js} +13 -10
- package/lib/types.d.ts +1 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/utils/styled.d.ts +46 -6
- package/lib/utils/styled.d.ts.map +1 -1
- package/lib/utils/styled.js +19 -4
- package/package.json +9 -5
- package/src/components/AlertDialog/AlertDialog.namespace.ts +22 -0
- package/src/components/AlertDialog/AlertDialog.test.tsx +226 -0
- package/src/components/AlertDialog/AlertDialog.tsx +374 -0
- package/src/components/AlertDialog/index.ts +24 -0
- package/src/components/AppBar/AppBar.namespace.ts +1 -0
- package/src/components/AppBar/AppBar.test.tsx +252 -3
- package/src/components/AppBar/AppBar.tsx +108 -25
- package/src/components/AppBar/context.ts +3 -1
- package/src/components/AppBar/index.ts +1 -0
- package/src/components/AppBar/useAppBar.ts +21 -15
- package/src/components/AttachmentDisplay/AttachmentDisplay.tsx +886 -0
- package/src/components/AttachmentDisplay/index.ts +48 -0
- package/src/components/AttachmentInput/AttachmentInput.tsx +1001 -0
- package/src/components/AttachmentInput/index.ts +49 -0
- package/src/components/BottomSheet/BottomSheet.test.tsx +22 -7
- package/src/components/BottomSheet/BottomSheet.tsx +1 -19
- package/src/components/Box/Box.test.tsx +104 -0
- package/src/components/Box/Box.tsx +9 -6
- package/src/components/ChipTabs/ChipTabs.namespace.ts +14 -0
- package/src/components/ChipTabs/ChipTabs.tsx +119 -0
- package/src/components/ChipTabs/index.ts +16 -0
- package/src/components/ContextualFloatingButton/ContextualFloatingButton.test.tsx +63 -0
- package/src/components/ContextualFloatingButton/ContextualFloatingButton.tsx +232 -0
- package/src/components/ContextualFloatingButton/index.ts +4 -0
- package/src/components/Dialog/Dialog.namespace.ts +24 -0
- package/src/components/Dialog/Dialog.test.tsx +172 -0
- package/src/components/Dialog/Dialog.tsx +373 -0
- package/src/components/Dialog/index.ts +26 -0
- package/src/components/FloatingActionButton/FloatingActionButton.namespace.ts +8 -0
- package/src/components/FloatingActionButton/FloatingActionButton.test.tsx +59 -0
- package/src/components/FloatingActionButton/FloatingActionButton.tsx +207 -0
- package/src/components/FloatingActionButton/index.ts +10 -0
- package/src/components/HelpBubble/HelpBubble.namespace.ts +24 -0
- package/src/components/HelpBubble/HelpBubble.tsx +998 -0
- package/src/components/HelpBubble/index.ts +26 -0
- package/src/components/Icon/Icon.test.tsx +0 -3
- package/src/components/Icon/Icon.tsx +11 -1
- package/src/components/IdentityPlaceholder/IdentityPlaceholder.namespace.ts +6 -0
- package/src/components/IdentityPlaceholder/IdentityPlaceholder.tsx +103 -0
- package/src/components/IdentityPlaceholder/assets.d.ts +4 -0
- package/src/components/IdentityPlaceholder/identity-placeholder-business.webp +0 -0
- package/src/components/IdentityPlaceholder/identity-placeholder-person.webp +0 -0
- package/src/components/IdentityPlaceholder/index.ts +8 -0
- package/src/components/InputButton/InputButton.test.tsx +128 -1
- package/src/components/InputButton/InputButton.tsx +60 -9
- package/src/components/List/List.test.tsx +54 -2
- package/src/components/List/List.tsx +43 -10
- package/src/components/Menu/Menu.tsx +14 -4
- package/src/components/Menu/__tests__/Menu.test.tsx +34 -0
- package/src/components/PageBanner/PageBanner.test.tsx +110 -3
- package/src/components/PageBanner/PageBanner.tsx +83 -19
- package/src/components/QuantityPicker/QuantityPicker.namespace.ts +10 -0
- package/src/components/QuantityPicker/QuantityPicker.test.tsx +235 -0
- package/src/components/QuantityPicker/QuantityPicker.tsx +605 -0
- package/src/components/QuantityPicker/index.ts +14 -0
- package/src/components/ReactionButton/ReactionButton.feedback.test.tsx +50 -0
- package/src/components/ReactionButton/ReactionButton.test.tsx +5 -3
- package/src/components/ReactionButton/ReactionButton.tsx +20 -12
- package/src/components/ScrollFog/ScrollFog.test.tsx +222 -0
- package/src/components/ScrollFog/ScrollFog.tsx +172 -0
- package/src/components/ScrollFog/index.ts +1 -0
- package/src/components/Select/Select.namespace.ts +34 -0
- package/src/components/Select/Select.test.tsx +297 -0
- package/src/components/Select/Select.tsx +1483 -0
- package/src/components/Select/index.ts +39 -0
- package/src/components/Skeleton/Skeleton.test.tsx +64 -0
- package/src/components/Skeleton/Skeleton.tsx +45 -0
- package/src/components/Skeleton/index.ts +1 -0
- package/src/components/Slider/Slider.namespace.ts +26 -0
- package/src/components/Slider/Slider.test.tsx +260 -0
- package/src/components/Slider/Slider.tsx +897 -0
- package/src/components/Slider/Slider.utils.ts +167 -0
- package/src/components/Slider/index.ts +30 -0
- package/src/components/Stack/Stack.test.tsx +7 -0
- package/src/components/Stack/Stack.tsx +11 -10
- package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +21 -1
- package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +30 -7
- package/src/components/Switch/Switch.tsx +14 -13
- package/src/components/Tabs/Tabs.test.tsx +218 -1
- package/src/components/Tabs/Tabs.tsx +346 -75
- package/src/components/Tabs/Tabs.utils.ts +51 -1
- package/src/components/Tabs/index.ts +17 -1
- package/src/components/index.ts +14 -0
- package/src/hooks/useControllableState.ts +1 -43
- package/src/hooks/usePressTap.test.ts +23 -11
- package/src/hooks/usePressTap.ts +1 -60
- package/src/hooks/useSafeArea.test.ts +13 -1
- package/src/hooks/useSafeArea.ts +25 -23
- package/src/types.ts +1 -0
- package/src/utils/styled.ts +127 -9
|
@@ -2,6 +2,7 @@ import "@testing-library/jest-dom";
|
|
|
2
2
|
import * as React from "@lynx-js/react";
|
|
3
3
|
import type { MainThread } from "@lynx-js/types";
|
|
4
4
|
import type { LynxIconElementProps } from "../../types";
|
|
5
|
+
import { Fragment, useEffect } from "@lynx-js/react";
|
|
5
6
|
import {
|
|
6
7
|
act,
|
|
7
8
|
fireEvent,
|
|
@@ -21,7 +22,9 @@ interface TestLynxGlobal {
|
|
|
21
22
|
lynx?: {
|
|
22
23
|
__globalProps?: {
|
|
23
24
|
safeAreaInsetTop?: number;
|
|
25
|
+
safeAreaInsetRight?: number;
|
|
24
26
|
safeAreaInsetBottom?: number;
|
|
27
|
+
safeAreaInsetLeft?: number;
|
|
25
28
|
};
|
|
26
29
|
};
|
|
27
30
|
lynxTestingEnv?: {
|
|
@@ -152,11 +155,22 @@ describe("AppBar", () => {
|
|
|
152
155
|
render(<AppBar.Root theme="cupertino" />);
|
|
153
156
|
|
|
154
157
|
expectStyle(getAppBarRoot().style, {
|
|
155
|
-
height: "calc(
|
|
158
|
+
height: "calc(103px)",
|
|
156
159
|
"padding-top": "47px",
|
|
157
160
|
});
|
|
158
161
|
});
|
|
159
162
|
|
|
163
|
+
it("adds left and right safe area insets to the root padding", () => {
|
|
164
|
+
setGlobalProps({ safeAreaInsetRight: 0, safeAreaInsetLeft: 59 });
|
|
165
|
+
|
|
166
|
+
render(<AppBar.Root theme="cupertino" />);
|
|
167
|
+
|
|
168
|
+
expectStyle(getAppBarRoot().style, {
|
|
169
|
+
"padding-left": "calc(75px)",
|
|
170
|
+
"padding-right": "calc(16px)",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
160
174
|
it("does not read the safe area CSS variable override as the JS layout source", () => {
|
|
161
175
|
setGlobalProps({ safeAreaInsetTop: 47 });
|
|
162
176
|
|
|
@@ -168,7 +182,7 @@ describe("AppBar", () => {
|
|
|
168
182
|
);
|
|
169
183
|
|
|
170
184
|
expectStyle(getAppBarRoot().style, {
|
|
171
|
-
height: "calc(
|
|
185
|
+
height: "calc(103px)",
|
|
172
186
|
"padding-top": "47px",
|
|
173
187
|
});
|
|
174
188
|
});
|
|
@@ -239,6 +253,8 @@ describe("AppBar", () => {
|
|
|
239
253
|
});
|
|
240
254
|
|
|
241
255
|
it("updates centered title padding from left and right layout widths", async () => {
|
|
256
|
+
setGlobalProps({ safeAreaInsetRight: 0, safeAreaInsetLeft: 59 });
|
|
257
|
+
|
|
242
258
|
render(
|
|
243
259
|
<AppBar.Root>
|
|
244
260
|
<AppBar.Left>
|
|
@@ -268,9 +284,242 @@ describe("AppBar", () => {
|
|
|
268
284
|
});
|
|
269
285
|
|
|
270
286
|
await waitFor(() => {
|
|
271
|
-
expect(main).toHaveStyle({
|
|
287
|
+
expect(main).toHaveStyle({
|
|
288
|
+
paddingLeft: "calc(59px + 88px)",
|
|
289
|
+
paddingRight: "calc(0px + 88px)",
|
|
290
|
+
});
|
|
272
291
|
});
|
|
273
292
|
});
|
|
293
|
+
|
|
294
|
+
it.each([
|
|
295
|
+
"left",
|
|
296
|
+
"right",
|
|
297
|
+
] as const)("bleeds both sides of a single button in the %s slot", (side) => {
|
|
298
|
+
const Slot = side === "left" ? AppBar.Left : AppBar.Right;
|
|
299
|
+
render(
|
|
300
|
+
<AppBar.Root>
|
|
301
|
+
<Slot>
|
|
302
|
+
<AppBar.IconButton accessibility-label="Back" />
|
|
303
|
+
</Slot>
|
|
304
|
+
</AppBar.Root>,
|
|
305
|
+
);
|
|
306
|
+
const button = getAppBarRoot().querySelector(".seed-app-bar__iconButton");
|
|
307
|
+
expect(button).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
308
|
+
expect(button).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it.each([
|
|
312
|
+
"left",
|
|
313
|
+
"right",
|
|
314
|
+
] as const)("bleeds only the outer sides of buttons in the %s slot", (side) => {
|
|
315
|
+
const Slot = side === "left" ? AppBar.Left : AppBar.Right;
|
|
316
|
+
render(
|
|
317
|
+
<AppBar.Root>
|
|
318
|
+
<Slot>
|
|
319
|
+
<AppBar.IconButton accessibility-label="Search" />
|
|
320
|
+
<AppBar.IconButton accessibility-label="Share" />
|
|
321
|
+
<AppBar.IconButton accessibility-label="Close" />
|
|
322
|
+
</Slot>
|
|
323
|
+
</AppBar.Root>,
|
|
324
|
+
);
|
|
325
|
+
const buttons = getAppBarRoot().querySelectorAll(".seed-app-bar__iconButton");
|
|
326
|
+
expect(buttons).toHaveLength(3);
|
|
327
|
+
expect(buttons[0]).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
328
|
+
expect(buttons[0]).not.toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
329
|
+
expect(buttons[1]).not.toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
330
|
+
expect(buttons[1]).not.toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
331
|
+
expect(buttons[2]).not.toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
332
|
+
expect(buttons[2]).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("updates both edges when a conditional sibling changes without remounting the button", () => {
|
|
336
|
+
const mount = vi.fn();
|
|
337
|
+
function WrappedButton() {
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
mount();
|
|
340
|
+
}, []);
|
|
341
|
+
return <AppBar.IconButton accessibility-label="Back" />;
|
|
342
|
+
}
|
|
343
|
+
function Example({ showCustom }: { showCustom: boolean }) {
|
|
344
|
+
return (
|
|
345
|
+
<AppBar.Root>
|
|
346
|
+
<AppBar.Left>
|
|
347
|
+
<WrappedButton />
|
|
348
|
+
{showCustom && (
|
|
349
|
+
<AppBar.Slot>
|
|
350
|
+
<text>Done</text>
|
|
351
|
+
</AppBar.Slot>
|
|
352
|
+
)}
|
|
353
|
+
</AppBar.Left>
|
|
354
|
+
</AppBar.Root>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const { rerender } = render(<Example showCustom={false} />);
|
|
358
|
+
const button = () => getAppBarRoot().querySelector(".seed-app-bar__iconButton");
|
|
359
|
+
expect(button()).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
360
|
+
expect(button()).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
361
|
+
rerender(<Example showCustom />);
|
|
362
|
+
expect(button()).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
363
|
+
expect(button()).not.toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
364
|
+
rerender(<Example showCustom={false} />);
|
|
365
|
+
expect(button()).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
366
|
+
expect(mount).toHaveBeenCalledTimes(1);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("does not bleed a custom slot sitting at the slot edge", () => {
|
|
370
|
+
render(
|
|
371
|
+
<AppBar.Root>
|
|
372
|
+
<AppBar.Right>
|
|
373
|
+
<AppBar.Slot>
|
|
374
|
+
<text>Done</text>
|
|
375
|
+
</AppBar.Slot>
|
|
376
|
+
</AppBar.Right>
|
|
377
|
+
</AppBar.Root>,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const custom = getAppBarRoot().querySelector<HTMLElement>(".seed-app-bar__custom");
|
|
381
|
+
expect(custom).toBeInTheDocument();
|
|
382
|
+
expect(custom!.style.getPropertyValue("margin-left")).toBe("");
|
|
383
|
+
expect(custom!.style.getPropertyValue("margin-right")).toBe("");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("bleeds the trailing icon button but not a preceding custom slot", () => {
|
|
387
|
+
render(
|
|
388
|
+
<AppBar.Root>
|
|
389
|
+
<AppBar.Right>
|
|
390
|
+
<AppBar.Slot>
|
|
391
|
+
<text>Done</text>
|
|
392
|
+
</AppBar.Slot>
|
|
393
|
+
<AppBar.IconButton accessibility-label="Close" />
|
|
394
|
+
</AppBar.Right>
|
|
395
|
+
</AppBar.Root>,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const root = getAppBarRoot();
|
|
399
|
+
const custom = root.querySelector<HTMLElement>(".seed-app-bar__custom");
|
|
400
|
+
const iconButton = root.querySelector<HTMLElement>(".seed-app-bar__iconButton");
|
|
401
|
+
expect(custom!.style.getPropertyValue("margin-right")).toBe("");
|
|
402
|
+
expect(iconButton).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it.each([
|
|
406
|
+
"leading",
|
|
407
|
+
"trailing",
|
|
408
|
+
"both",
|
|
409
|
+
] as const)("respects explicit edge=%s over automatic edges", (edge) => {
|
|
410
|
+
render(
|
|
411
|
+
<AppBar.Root>
|
|
412
|
+
<AppBar.Left>
|
|
413
|
+
<AppBar.IconButton accessibility-label="Back" edge={edge} />
|
|
414
|
+
</AppBar.Left>
|
|
415
|
+
</AppBar.Root>,
|
|
416
|
+
);
|
|
417
|
+
const button = getAppBarRoot().querySelector(".seed-app-bar__iconButton");
|
|
418
|
+
expect(button?.classList.contains("seed-app-bar__icon-button-edge-leading")).toBe(
|
|
419
|
+
edge !== "trailing",
|
|
420
|
+
);
|
|
421
|
+
expect(button?.classList.contains("seed-app-bar__icon-button-edge-trailing")).toBe(
|
|
422
|
+
edge !== "leading",
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("preserves caller styles when automatic edge compensation applies", () => {
|
|
427
|
+
render(
|
|
428
|
+
<AppBar.Root>
|
|
429
|
+
<AppBar.Left>
|
|
430
|
+
<AppBar.IconButton
|
|
431
|
+
accessibility-label="Back"
|
|
432
|
+
{...{ style: { width: "60px", opacity: 0.5 } }}
|
|
433
|
+
/>
|
|
434
|
+
</AppBar.Left>
|
|
435
|
+
</AppBar.Root>,
|
|
436
|
+
);
|
|
437
|
+
const button = getAppBarRoot().querySelector<HTMLElement>(".seed-app-bar__iconButton");
|
|
438
|
+
expect(button).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
439
|
+
expectStyle(button!.style, { width: "60px", opacity: "0.5" });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("finds the physical edge through nested fragments and conditional children", () => {
|
|
443
|
+
render(
|
|
444
|
+
<AppBar.Root>
|
|
445
|
+
<AppBar.Right>
|
|
446
|
+
<Fragment>
|
|
447
|
+
{false && <AppBar.IconButton accessibility-label="Hidden" />}
|
|
448
|
+
<AppBar.IconButton accessibility-label="Search" />
|
|
449
|
+
<Fragment key="actions">
|
|
450
|
+
<AppBar.IconButton accessibility-label="Close" />
|
|
451
|
+
{null}
|
|
452
|
+
<Fragment />
|
|
453
|
+
</Fragment>
|
|
454
|
+
</Fragment>
|
|
455
|
+
</AppBar.Right>
|
|
456
|
+
</AppBar.Root>,
|
|
457
|
+
);
|
|
458
|
+
const buttons = getAppBarRoot().querySelectorAll<HTMLElement>(".seed-app-bar__iconButton");
|
|
459
|
+
expect(buttons).toHaveLength(2);
|
|
460
|
+
expect(buttons[0]).toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
461
|
+
expect(buttons[0]).not.toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
462
|
+
expect(buttons[1]).not.toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
463
|
+
expect(buttons[1]).toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("does not leak edge to native children or bleed buttons inside custom layout boxes", () => {
|
|
467
|
+
render(
|
|
468
|
+
<AppBar.Root>
|
|
469
|
+
<AppBar.Left>
|
|
470
|
+
<view id="native-edge">
|
|
471
|
+
<AppBar.IconButton accessibility-label="Nested" />
|
|
472
|
+
</view>
|
|
473
|
+
</AppBar.Left>
|
|
474
|
+
<AppBar.Right>
|
|
475
|
+
<AppBar.Slot>
|
|
476
|
+
<AppBar.IconButton accessibility-label="Custom" />
|
|
477
|
+
</AppBar.Slot>
|
|
478
|
+
</AppBar.Right>
|
|
479
|
+
</AppBar.Root>,
|
|
480
|
+
);
|
|
481
|
+
const root = getAppBarRoot();
|
|
482
|
+
expect(root.querySelector("#native-edge")).not.toHaveAttribute("edge");
|
|
483
|
+
expect(root.querySelector("[edge]")).toBeNull();
|
|
484
|
+
for (const button of root.querySelectorAll(".seed-app-bar__iconButton")) {
|
|
485
|
+
expect(button).not.toHaveClass("seed-app-bar__icon-button-edge-leading");
|
|
486
|
+
expect(button).not.toHaveClass("seed-app-bar__icon-button-edge-trailing");
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("passes edge through wrapper components without changing their props or remounting", () => {
|
|
491
|
+
const mount = vi.fn();
|
|
492
|
+
const received = vi.fn();
|
|
493
|
+
function WrappedButton(props: { edge?: "leading" | "trailing" }) {
|
|
494
|
+
received(props);
|
|
495
|
+
useEffect(() => {
|
|
496
|
+
mount();
|
|
497
|
+
}, []);
|
|
498
|
+
return <AppBar.IconButton accessibility-label="Wrapped" {...props} />;
|
|
499
|
+
}
|
|
500
|
+
const { rerender } = render(
|
|
501
|
+
<AppBar.Root>
|
|
502
|
+
<AppBar.Left>
|
|
503
|
+
<WrappedButton />
|
|
504
|
+
</AppBar.Left>
|
|
505
|
+
</AppBar.Root>,
|
|
506
|
+
);
|
|
507
|
+
expect(getAppBarRoot().querySelector(".seed-app-bar__iconButton")).toHaveClass(
|
|
508
|
+
"seed-app-bar__icon-button-edge-leading",
|
|
509
|
+
);
|
|
510
|
+
expect(received.mock.lastCall?.[0]).not.toHaveProperty("edge");
|
|
511
|
+
rerender(
|
|
512
|
+
<AppBar.Root>
|
|
513
|
+
<AppBar.Left>
|
|
514
|
+
<WrappedButton edge="trailing" />
|
|
515
|
+
</AppBar.Left>
|
|
516
|
+
</AppBar.Root>,
|
|
517
|
+
);
|
|
518
|
+
expect(getAppBarRoot().querySelector(".seed-app-bar__iconButton")).toHaveClass(
|
|
519
|
+
"seed-app-bar__icon-button-edge-trailing",
|
|
520
|
+
);
|
|
521
|
+
expect(mount).toHaveBeenCalledTimes(1);
|
|
522
|
+
});
|
|
274
523
|
});
|
|
275
524
|
|
|
276
525
|
describe("AppBar icon recipe ownership", () => {
|
|
@@ -21,6 +21,7 @@ import { IconSlotProvider } from "../Icon/Icon";
|
|
|
21
21
|
import { AppBarProvider, useAppBarContext } from "./context";
|
|
22
22
|
import { getLayoutWidth, getMainLayoutStyle, useAppBar } from "./useAppBar";
|
|
23
23
|
import { mergeProps } from "../../utils/merge-props";
|
|
24
|
+
import { useScaleFeedback } from "../../hooks/useScaleFeedback";
|
|
24
25
|
|
|
25
26
|
type AppBarClassNames = ReturnType<typeof appBar>;
|
|
26
27
|
type AppBarMainClassNames = ReturnType<typeof appBarMain>;
|
|
@@ -49,6 +50,66 @@ function useAppBarMainClassNames(consumer: string): AppBarMainClassNames {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 아이콘 버튼의 bleed 보정 방향. `leading`은 왼쪽, `trailing`은 오른쪽, `both`는 양쪽.
|
|
55
|
+
*/
|
|
56
|
+
export type AppBarEdge = "leading" | "trailing" | "both";
|
|
57
|
+
|
|
58
|
+
const AppBarEdgeContext = React.createContext<AppBarEdge | undefined>(undefined);
|
|
59
|
+
|
|
60
|
+
/** Fragments and arrays do not create layout boxes; retain their keys and nesting. */
|
|
61
|
+
function countEdgeChildren(children: React.ReactNode): number {
|
|
62
|
+
if (children == null || children === "" || typeof children === "boolean") return 0;
|
|
63
|
+
if (Array.isArray(children))
|
|
64
|
+
return children.reduce((count, child) => count + countEdgeChildren(child), 0);
|
|
65
|
+
if (
|
|
66
|
+
React.isValidElement<{ children?: React.ReactNode }>(children) &&
|
|
67
|
+
children.type === React.Fragment
|
|
68
|
+
) {
|
|
69
|
+
return countEdgeChildren(children.props.children);
|
|
70
|
+
}
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Lynx lacks :first-child/:last-child; pass both slot edges through Context to match
|
|
75
|
+
// web spacing without shrinking the icon button's touch target.
|
|
76
|
+
function provideEdgeToChildren(children: React.ReactNode): React.ReactNode {
|
|
77
|
+
const lastIndex = countEdgeChildren(children) - 1;
|
|
78
|
+
let index = 0;
|
|
79
|
+
|
|
80
|
+
function visit(child: React.ReactNode): React.ReactNode {
|
|
81
|
+
if (child == null || child === "" || typeof child === "boolean") return child;
|
|
82
|
+
if (Array.isArray(child)) return child.map(visit);
|
|
83
|
+
if (
|
|
84
|
+
React.isValidElement<{ children?: React.ReactNode }>(child) &&
|
|
85
|
+
child.type === React.Fragment
|
|
86
|
+
) {
|
|
87
|
+
return React.cloneElement(child, { children: visit(child.props.children) });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const childIndex = index++;
|
|
91
|
+
const edge =
|
|
92
|
+
childIndex === 0
|
|
93
|
+
? childIndex === lastIndex
|
|
94
|
+
? "both"
|
|
95
|
+
: "leading"
|
|
96
|
+
: childIndex === lastIndex
|
|
97
|
+
? "trailing"
|
|
98
|
+
: undefined;
|
|
99
|
+
if (!React.isValidElement(child)) return child;
|
|
100
|
+
// Native boxes and custom slots own their layout; do not pass an automatic edge into them.
|
|
101
|
+
const automaticEdge =
|
|
102
|
+
typeof child.type !== "string" && child.type !== AppBarSlot ? edge : undefined;
|
|
103
|
+
return (
|
|
104
|
+
<AppBarEdgeContext.Provider key={child.key} value={automaticEdge}>
|
|
105
|
+
{child}
|
|
106
|
+
</AppBarEdgeContext.Provider>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return visit(children);
|
|
111
|
+
}
|
|
112
|
+
|
|
52
113
|
////////////////////////////////////////////////////////////////////////////////////
|
|
53
114
|
|
|
54
115
|
export interface AppBarRootProps extends AppBarVariantProps, LynxStyledElementProps {}
|
|
@@ -67,7 +128,7 @@ export const AppBarRoot = React.forwardRef<unknown, AppBarRootProps>((props, ref
|
|
|
67
128
|
className={clsx(classNames.root, className)}
|
|
68
129
|
style={
|
|
69
130
|
{
|
|
70
|
-
"--seed-safe-area-top": contextValue.safeAreaInsetTop,
|
|
131
|
+
"--seed-safe-area-top": contextValue.safeArea.safeAreaInsetTop,
|
|
71
132
|
"--centered-title-padding-x": contextValue.centeredTitlePaddingX,
|
|
72
133
|
...rootLayoutStyle,
|
|
73
134
|
...style,
|
|
@@ -114,7 +175,7 @@ export const AppBarLeft = React.forwardRef<unknown, AppBarLeftProps>((props, ref
|
|
|
114
175
|
)}
|
|
115
176
|
className={clsx(classNames.left, className)}
|
|
116
177
|
>
|
|
117
|
-
{children}
|
|
178
|
+
{provideEdgeToChildren(children)}
|
|
118
179
|
</view>
|
|
119
180
|
);
|
|
120
181
|
});
|
|
@@ -149,7 +210,7 @@ export const AppBarRight = React.forwardRef<unknown, AppBarRightProps>((props, r
|
|
|
149
210
|
)}
|
|
150
211
|
className={clsx(classNames.right, className)}
|
|
151
212
|
>
|
|
152
|
-
{children}
|
|
213
|
+
{provideEdgeToChildren(children)}
|
|
153
214
|
</view>
|
|
154
215
|
);
|
|
155
216
|
});
|
|
@@ -160,8 +221,7 @@ AppBarRight.displayName = "AppBarRight";
|
|
|
160
221
|
export interface AppBarMainProps extends AppBarMainVariantProps, LynxStyledElementProps {}
|
|
161
222
|
|
|
162
223
|
export const AppBarMain = React.forwardRef<unknown, AppBarMainProps>((props, ref) => {
|
|
163
|
-
const { centeredTitlePaddingX,
|
|
164
|
-
useAppBarContext("AppBarMain");
|
|
224
|
+
const { centeredTitlePaddingX, safeArea, sharedVariantProps } = useAppBarContext("AppBarMain");
|
|
165
225
|
const [variantProps, otherProps] = appBarMain.splitVariantProps({
|
|
166
226
|
...sharedVariantProps,
|
|
167
227
|
...props,
|
|
@@ -169,13 +229,6 @@ export const AppBarMain = React.forwardRef<unknown, AppBarMainProps>((props, ref
|
|
|
169
229
|
const resolvedTheme = variantProps.theme ?? "cupertino";
|
|
170
230
|
const classNames = appBarMain(variantProps);
|
|
171
231
|
const { children, className, style, ...nativeProps } = otherProps;
|
|
172
|
-
const centeredTitleStyle =
|
|
173
|
-
variantProps.theme === "cupertino"
|
|
174
|
-
? {
|
|
175
|
-
paddingLeft: centeredTitlePaddingX,
|
|
176
|
-
paddingRight: centeredTitlePaddingX,
|
|
177
|
-
}
|
|
178
|
-
: undefined;
|
|
179
232
|
|
|
180
233
|
return (
|
|
181
234
|
<AppBarMainClassNamesProvider value={classNames}>
|
|
@@ -184,8 +237,7 @@ export const AppBarMain = React.forwardRef<unknown, AppBarMainProps>((props, ref
|
|
|
184
237
|
className={clsx(classNames.root, className)}
|
|
185
238
|
style={
|
|
186
239
|
{
|
|
187
|
-
...
|
|
188
|
-
...getMainLayoutStyle(resolvedTheme, safeAreaInsetTop),
|
|
240
|
+
...getMainLayoutStyle(resolvedTheme, safeArea, centeredTitlePaddingX),
|
|
189
241
|
...style,
|
|
190
242
|
} as LynxViewProps["style"]
|
|
191
243
|
}
|
|
@@ -233,11 +285,18 @@ AppBarSubtitle.displayName = "AppBarSubtitle";
|
|
|
233
285
|
|
|
234
286
|
////////////////////////////////////////////////////////////////////////////////////
|
|
235
287
|
|
|
236
|
-
export interface AppBarIconButtonProps
|
|
288
|
+
export interface AppBarIconButtonProps
|
|
289
|
+
extends Omit<LynxElementProps, "flatten">,
|
|
290
|
+
LynxPressableProps {
|
|
237
291
|
icon?: React.ReactElement<LynxIconElementProps>;
|
|
238
292
|
"accessibility-label"?: LynxViewProps["accessibility-label"];
|
|
239
293
|
"accessibility-element"?: LynxViewProps["accessibility-element"];
|
|
240
294
|
"accessibility-traits"?: LynxViewProps["accessibility-traits"];
|
|
295
|
+
/**
|
|
296
|
+
* 슬롯의 첫·마지막 자식이 아이콘 버튼이면 해당 방향을, 유일한 자식이면 양쪽을 자동 보정한다.
|
|
297
|
+
* 명시하면 `AppBarLeft` / `AppBarRight`에서 감지한 자동 보정 방향을 덮어쓴다.
|
|
298
|
+
*/
|
|
299
|
+
edge?: AppBarEdge;
|
|
241
300
|
}
|
|
242
301
|
|
|
243
302
|
export const AppBarIconButton = React.forwardRef<unknown, AppBarIconButtonProps>((props, ref) => {
|
|
@@ -245,12 +304,16 @@ export const AppBarIconButton = React.forwardRef<unknown, AppBarIconButtonProps>
|
|
|
245
304
|
children,
|
|
246
305
|
className,
|
|
247
306
|
icon,
|
|
307
|
+
edge,
|
|
248
308
|
"accessibility-element": accessibilityElement = true,
|
|
249
309
|
"accessibility-label": accessibilityLabel,
|
|
250
310
|
"accessibility-traits": accessibilityTraits = "button",
|
|
251
311
|
...nativeProps
|
|
252
312
|
} = props;
|
|
253
313
|
const classNames = useAppBarClassNames("AppBarIconButton");
|
|
314
|
+
const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback();
|
|
315
|
+
const automaticEdge = React.useContext(AppBarEdgeContext);
|
|
316
|
+
const resolvedEdge = edge ?? automaticEdge;
|
|
254
317
|
|
|
255
318
|
if (process.env.NODE_ENV !== "production" && accessibilityElement && !accessibilityLabel) {
|
|
256
319
|
console.warn("AppBarIconButton requires `accessibility-label` for accessibility.");
|
|
@@ -258,11 +321,24 @@ export const AppBarIconButton = React.forwardRef<unknown, AppBarIconButtonProps>
|
|
|
258
321
|
|
|
259
322
|
return (
|
|
260
323
|
<view
|
|
261
|
-
{...mergeProps(
|
|
324
|
+
{...mergeProps(
|
|
325
|
+
ref ? { ref: ref as LynxViewRef } : {},
|
|
326
|
+
scaleFeedbackTriggerProps,
|
|
327
|
+
scaleFeedbackTargetProps,
|
|
328
|
+
nativeProps,
|
|
329
|
+
)}
|
|
330
|
+
flatten={false}
|
|
262
331
|
accessibility-element={accessibilityElement}
|
|
263
332
|
accessibility-label={accessibilityLabel}
|
|
264
333
|
accessibility-traits={accessibilityTraits}
|
|
265
|
-
className={clsx(
|
|
334
|
+
className={clsx(
|
|
335
|
+
classNames.iconButton,
|
|
336
|
+
(resolvedEdge === "leading" || resolvedEdge === "both") &&
|
|
337
|
+
"seed-app-bar__icon-button-edge-leading",
|
|
338
|
+
(resolvedEdge === "trailing" || resolvedEdge === "both") &&
|
|
339
|
+
"seed-app-bar__icon-button-edge-trailing",
|
|
340
|
+
className,
|
|
341
|
+
)}
|
|
266
342
|
>
|
|
267
343
|
<IconSlotProvider value={{ classNames: { icon: classNames.icon }, deps: [classNames.icon] }}>
|
|
268
344
|
{icon ? <Icon icon={icon} /> : children}
|
|
@@ -272,19 +348,26 @@ export const AppBarIconButton = React.forwardRef<unknown, AppBarIconButtonProps>
|
|
|
272
348
|
});
|
|
273
349
|
AppBarIconButton.displayName = "AppBarIconButton";
|
|
274
350
|
|
|
275
|
-
export interface AppBarSlotProps extends LynxStyledElementProps {
|
|
351
|
+
export interface AppBarSlotProps extends LynxStyledElementProps {
|
|
352
|
+
/**
|
|
353
|
+
* @internal 커스텀 슬롯에서는 bleed 보정을 적용하지 않는다. 전달된 값은 native 속성에서 제외한다.
|
|
354
|
+
*/
|
|
355
|
+
edge?: AppBarEdge;
|
|
356
|
+
}
|
|
276
357
|
|
|
277
358
|
export const AppBarSlot = React.forwardRef<unknown, AppBarSlotProps>((props, ref) => {
|
|
278
|
-
const { children, className, ...nativeProps } = props;
|
|
359
|
+
const { children, className, edge: _edge, ...nativeProps } = props;
|
|
279
360
|
const classNames = useAppBarClassNames("AppBarSlot");
|
|
280
361
|
|
|
281
362
|
return (
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
363
|
+
<AppBarEdgeContext.Provider value={undefined}>
|
|
364
|
+
<view
|
|
365
|
+
{...mergeProps(ref ? { ref: ref as LynxViewRef } : {}, nativeProps)}
|
|
366
|
+
className={clsx(classNames.custom, className)}
|
|
367
|
+
>
|
|
368
|
+
{children}
|
|
369
|
+
</view>
|
|
370
|
+
</AppBarEdgeContext.Provider>
|
|
288
371
|
);
|
|
289
372
|
});
|
|
290
373
|
AppBarSlot.displayName = "AppBarSlot";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type AppBarMainVariantProps } from "@seed-design/lynx-css/recipes/app-bar-main";
|
|
2
2
|
import * as React from "@lynx-js/react";
|
|
3
3
|
|
|
4
|
+
import type { UseSafeAreaReturn } from "../../hooks/useSafeArea";
|
|
5
|
+
|
|
4
6
|
export type SharedAppBarVariantProps = Pick<
|
|
5
7
|
AppBarMainVariantProps,
|
|
6
8
|
"theme" | "tone" | "transitionStyle"
|
|
@@ -8,7 +10,7 @@ export type SharedAppBarVariantProps = Pick<
|
|
|
8
10
|
|
|
9
11
|
export interface AppBarContextValue {
|
|
10
12
|
centeredTitlePaddingX: string;
|
|
11
|
-
|
|
13
|
+
safeArea: UseSafeAreaReturn;
|
|
12
14
|
sharedVariantProps: SharedAppBarVariantProps;
|
|
13
15
|
setLeftWidth: (width: number) => void;
|
|
14
16
|
setRightWidth: (width: number) => void;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AppBarVariantProps } from "@seed-design/lynx-css/recipes/app-bar";
|
|
2
2
|
import type { AppBarMainVariantProps } from "@seed-design/lynx-css/recipes/app-bar-main";
|
|
3
|
+
import { topNavigation as topNavigationVars } from "@seed-design/lynx-css/vars/component";
|
|
3
4
|
import * as React from "@lynx-js/react";
|
|
4
5
|
|
|
5
|
-
import { useSafeArea } from "../../hooks/useSafeArea";
|
|
6
|
+
import { type UseSafeAreaReturn, useSafeArea } from "../../hooks/useSafeArea";
|
|
6
7
|
import type { LynxViewProps } from "../../types";
|
|
7
8
|
import type { AppBarContextValue, SharedAppBarVariantProps } from "./context";
|
|
8
9
|
|
|
@@ -14,10 +15,8 @@ type AppBarTheme = NonNullable<AppBarVariantProps["theme"]>;
|
|
|
14
15
|
type LayoutChangeHandler = NonNullable<LynxViewProps["bindlayoutchange"]>;
|
|
15
16
|
type AppBarStyleObject = Record<string, string | number>;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
android: "56px",
|
|
20
|
-
};
|
|
18
|
+
// Mirrors the recipe's `dimension.x4` as a literal: Lynx drops an inline `calc()` that contains `var()`.
|
|
19
|
+
const ROOT_PADDING_X = 16;
|
|
21
20
|
|
|
22
21
|
function getDefaultAppBarTheme(): AppBarTheme {
|
|
23
22
|
const globalSystemInfo = (globalThis as typeof globalThis & { SystemInfo?: LynxSystemInfo })
|
|
@@ -38,31 +37,38 @@ export function getLayoutWidth(event: Parameters<LayoutChangeHandler>[0]): numbe
|
|
|
38
37
|
return Math.max(0, nextWidth);
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
// The left/right areas sit inside the root padding, so the title clears that padding plus the wider area.
|
|
41
41
|
function getCenteredTitlePadding(leftWidth: number, rightWidth: number): string {
|
|
42
|
-
return `${Math.max(leftWidth, rightWidth)}px`;
|
|
42
|
+
return `${ROOT_PADDING_X + Math.max(leftWidth, rightWidth)}px`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function getRootLayoutStyle(
|
|
45
|
+
function getRootLayoutStyle(safeArea: UseSafeAreaReturn): AppBarStyleObject {
|
|
46
46
|
return {
|
|
47
|
-
height: `calc(${
|
|
48
|
-
paddingTop: safeAreaInsetTop,
|
|
47
|
+
height: `calc(${topNavigationVars.base.enabled.root.height} + ${safeArea.safeAreaInsetTop})`,
|
|
48
|
+
paddingTop: safeArea.safeAreaInsetTop,
|
|
49
|
+
paddingLeft: `calc(${ROOT_PADDING_X}px + ${safeArea.safeAreaInsetLeft})`,
|
|
50
|
+
paddingRight: `calc(${ROOT_PADDING_X}px + ${safeArea.safeAreaInsetRight})`,
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
// The title clears the same distance past each side's inset, so it stays centered in the safe area.
|
|
52
55
|
export function getMainLayoutStyle(
|
|
53
56
|
theme: AppBarMainVariantProps["theme"],
|
|
54
|
-
|
|
57
|
+
safeArea: UseSafeAreaReturn,
|
|
58
|
+
centeredTitlePaddingX: string,
|
|
55
59
|
): AppBarStyleObject | undefined {
|
|
56
60
|
if (theme !== "cupertino") return undefined;
|
|
57
61
|
|
|
58
62
|
return {
|
|
59
|
-
top: safeAreaInsetTop,
|
|
63
|
+
top: safeArea.safeAreaInsetTop,
|
|
60
64
|
bottom: "0px",
|
|
65
|
+
paddingLeft: `calc(${safeArea.safeAreaInsetLeft} + ${centeredTitlePaddingX})`,
|
|
66
|
+
paddingRight: `calc(${safeArea.safeAreaInsetRight} + ${centeredTitlePaddingX})`,
|
|
61
67
|
};
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export function useAppBar(variantProps: AppBarVariantProps) {
|
|
65
|
-
const
|
|
71
|
+
const safeArea = useSafeArea();
|
|
66
72
|
const [leftWidth, setLeftWidth] = React.useState(0);
|
|
67
73
|
const [rightWidth, setRightWidth] = React.useState(0);
|
|
68
74
|
const resolvedTheme = variantProps.theme ?? getDefaultAppBarTheme();
|
|
@@ -72,7 +78,7 @@ export function useAppBar(variantProps: AppBarVariantProps) {
|
|
|
72
78
|
theme: resolvedTheme,
|
|
73
79
|
};
|
|
74
80
|
const centeredTitlePaddingX = getCenteredTitlePadding(leftWidth, rightWidth);
|
|
75
|
-
const rootLayoutStyle = getRootLayoutStyle(
|
|
81
|
+
const rootLayoutStyle = getRootLayoutStyle(safeArea);
|
|
76
82
|
const sharedVariantProps = React.useMemo<SharedAppBarVariantProps>(
|
|
77
83
|
() => ({
|
|
78
84
|
theme: resolvedVariantProps.theme,
|
|
@@ -84,12 +90,12 @@ export function useAppBar(variantProps: AppBarVariantProps) {
|
|
|
84
90
|
const contextValue = React.useMemo<AppBarContextValue>(
|
|
85
91
|
() => ({
|
|
86
92
|
centeredTitlePaddingX,
|
|
87
|
-
|
|
93
|
+
safeArea,
|
|
88
94
|
sharedVariantProps,
|
|
89
95
|
setLeftWidth,
|
|
90
96
|
setRightWidth,
|
|
91
97
|
}),
|
|
92
|
-
[centeredTitlePaddingX,
|
|
98
|
+
[centeredTitlePaddingX, safeArea, sharedVariantProps],
|
|
93
99
|
);
|
|
94
100
|
|
|
95
101
|
return {
|