@seed-design/lynx-react 0.2.0-alpha.3 → 0.3.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 (179) hide show
  1. package/lib/_virtual/_rolldown/runtime.js +13 -0
  2. package/lib/components/ActionButton/ActionButton.d.ts +26 -33
  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 -31
  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 -29
  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 +5 -3
  84. package/lib/hooks/index.d.ts.map +1 -1
  85. package/lib/hooks/useControllableState.js +23 -23
  86. package/lib/hooks/useIconColor.d.ts +6 -3
  87. package/lib/hooks/useIconColor.d.ts.map +1 -1
  88. package/lib/hooks/useIconColor.js +34 -37
  89. package/lib/hooks/usePressTap.d.ts +11 -8
  90. package/lib/hooks/usePressTap.d.ts.map +1 -1
  91. package/lib/hooks/usePressTap.js +31 -31
  92. package/lib/hooks/useSafeArea.d.ts +19 -0
  93. package/lib/hooks/useSafeArea.d.ts.map +1 -0
  94. package/lib/hooks/useSafeArea.js +33 -0
  95. package/lib/hooks/useSeedClassName.d.ts +27 -0
  96. package/lib/hooks/useSeedClassName.d.ts.map +1 -0
  97. package/lib/hooks/useSeedClassName.js +32 -0
  98. package/lib/index.d.ts +4 -3
  99. package/lib/index.d.ts.map +1 -1
  100. package/lib/index.js +30 -3
  101. package/lib/types.d.ts +51 -0
  102. package/lib/types.d.ts.map +1 -0
  103. package/lib/utils/children.d.ts +3 -0
  104. package/lib/utils/children.d.ts.map +1 -0
  105. package/lib/utils/children.js +8 -0
  106. package/lib/utils/create-slot-recipe-context.d.ts +13 -12
  107. package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
  108. package/lib/utils/create-slot-recipe-context.jsx +88 -70
  109. package/lib/utils/get-seed-class-name.d.ts +9 -7
  110. package/lib/utils/get-seed-class-name.d.ts.map +1 -1
  111. package/lib/utils/get-seed-class-name.js +29 -37
  112. package/lib/utils/index.d.ts +4 -4
  113. package/lib/utils/resolve-recipe-token.js +45 -43
  114. package/lib/utils/split-multiple-variants-props.js +32 -32
  115. package/lib/utils/styled.d.ts +83 -0
  116. package/lib/utils/styled.d.ts.map +1 -0
  117. package/lib/utils/styled.js +144 -0
  118. package/package.json +10 -9
  119. package/src/components/ActionButton/ActionButton.test.tsx +167 -0
  120. package/src/components/ActionButton/ActionButton.tsx +210 -169
  121. package/src/components/AppBar/AppBar.namespace.ts +18 -0
  122. package/src/components/AppBar/AppBar.test.tsx +270 -0
  123. package/src/components/AppBar/AppBar.tsx +288 -0
  124. package/src/components/AppBar/context.ts +27 -0
  125. package/src/components/AppBar/index.ts +21 -0
  126. package/src/components/AppBar/useAppBar.ts +100 -0
  127. package/src/components/Badge/Badge.test.tsx +54 -0
  128. package/src/components/Badge/Badge.tsx +32 -0
  129. package/src/components/Badge/index.ts +1 -0
  130. package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
  131. package/src/components/BottomSheet/BottomSheet.tsx +120 -83
  132. package/src/components/Box/Box.test.tsx +85 -0
  133. package/src/components/Box/Box.tsx +39 -0
  134. package/src/components/Box/index.ts +1 -0
  135. package/src/components/Checkbox/Checkbox.tsx +106 -116
  136. package/src/components/Icon/Icon.tsx +265 -0
  137. package/src/components/Icon/index.ts +8 -0
  138. package/src/components/ProgressCircle/ProgressCircle.tsx +39 -25
  139. package/src/components/RadioGroup/RadioGroup.tsx +98 -117
  140. package/src/components/Stack/Stack.tsx +97 -0
  141. package/src/components/Stack/index.ts +1 -0
  142. package/src/components/Switch/Switch.tsx +59 -50
  143. package/src/components/TagGroup/TagGroup.tsx +27 -25
  144. package/src/components/Text/Text.tsx +64 -0
  145. package/src/components/Text/index.ts +1 -0
  146. package/src/components/index.ts +6 -0
  147. package/src/hooks/index.ts +2 -0
  148. package/src/hooks/{__tests__/useControllableState.test.ts → useControllableState.test.ts} +1 -1
  149. package/src/hooks/{__tests__/useIconColor.test.ts → useIconColor.test.ts} +1 -1
  150. package/src/hooks/useIconColor.ts +22 -12
  151. package/src/hooks/{__tests__/usePressTap.test.ts → usePressTap.test.ts} +1 -1
  152. package/src/hooks/usePressTap.ts +14 -10
  153. package/src/hooks/useSafeArea.test.ts +83 -0
  154. package/src/hooks/useSafeArea.ts +56 -0
  155. package/src/hooks/useSeedClassName.ts +35 -0
  156. package/src/index.ts +1 -0
  157. package/src/types.ts +76 -0
  158. package/src/utils/children.ts +13 -0
  159. package/src/utils/create-slot-recipe-context.test.tsx +96 -0
  160. package/src/utils/create-slot-recipe-context.tsx +55 -15
  161. package/src/utils/get-seed-class-name.ts +10 -12
  162. package/src/utils/{__tests__/split-multiple-variants-props.test.ts → split-multiple-variants-props.test.ts} +1 -1
  163. package/src/utils/styled.ts +442 -0
  164. package/lib/components/ActionButton/index.js +0 -1
  165. package/lib/components/BottomSheet/index.js +0 -2
  166. package/lib/components/Checkbox/index.js +0 -2
  167. package/lib/components/ProgressCircle/index.js +0 -2
  168. package/lib/components/RadioGroup/index.js +0 -2
  169. package/lib/components/Switch/index.js +0 -2
  170. package/lib/components/TagGroup/index.js +0 -2
  171. package/lib/components/index.js +0 -7
  172. package/lib/hooks/index.js +0 -3
  173. package/lib/utils/index.js +0 -4
  174. package/src/components/ActionButton/__tests__/ActionButton.test.tsx +0 -35
  175. package/src/components/BottomSheet/__tests__/BottomSheet.test.tsx +0 -119
  176. package/src/components/__tests__/ProgressCircle.test.ts +0 -33
  177. package/src/components/__tests__/exports.test.ts +0 -38
  178. package/src/components/__tests__/switch.test.ts +0 -34
  179. package/src/utils/__tests__/create-slot-recipe-context.test.tsx +0 -228
@@ -0,0 +1,100 @@
1
+ import type { AppBarVariantProps } from "@seed-design/lynx-css/recipes/app-bar";
2
+ import type { AppBarMainVariantProps } from "@seed-design/lynx-css/recipes/app-bar-main";
3
+ import * as React from "@lynx-js/react";
4
+
5
+ import { useSafeArea } from "../../hooks/useSafeArea";
6
+ import type { LynxViewProps } from "../../types";
7
+ import type { AppBarContextValue, SharedAppBarVariantProps } from "./context";
8
+
9
+ type LynxSystemInfo = { platform?: string };
10
+
11
+ declare const SystemInfo: LynxSystemInfo | undefined;
12
+
13
+ type AppBarTheme = NonNullable<AppBarVariantProps["theme"]>;
14
+ type LayoutChangeHandler = NonNullable<LynxViewProps["bindlayoutchange"]>;
15
+ type AppBarStyleObject = Record<string, string | number>;
16
+
17
+ const APP_BAR_HEIGHT_BY_THEME: Record<AppBarTheme, string> = {
18
+ cupertino: "44px",
19
+ android: "56px",
20
+ };
21
+
22
+ function getDefaultAppBarTheme(): AppBarTheme {
23
+ const globalSystemInfo = (globalThis as typeof globalThis & { SystemInfo?: LynxSystemInfo })
24
+ .SystemInfo;
25
+ const systemInfo =
26
+ globalSystemInfo ?? (typeof SystemInfo === "undefined" ? undefined : SystemInfo);
27
+
28
+ if (systemInfo == null) return "cupertino";
29
+
30
+ return systemInfo.platform === "Android" ? "android" : "cupertino";
31
+ }
32
+
33
+ export function getLayoutWidth(event: Parameters<LayoutChangeHandler>[0]): number | null {
34
+ const eventWithWidth = event as Parameters<LayoutChangeHandler>[0] & { width?: number };
35
+ const nextWidth = event.detail?.width ?? event.params?.width ?? eventWithWidth.width;
36
+ if (typeof nextWidth !== "number" || !Number.isFinite(nextWidth)) return null;
37
+
38
+ return Math.max(0, nextWidth);
39
+ }
40
+
41
+ function getCenteredTitlePadding(leftWidth: number, rightWidth: number): string {
42
+ return `${Math.max(leftWidth, rightWidth)}px`;
43
+ }
44
+
45
+ function getRootLayoutStyle(theme: AppBarTheme, safeAreaInsetTop: string): AppBarStyleObject {
46
+ return {
47
+ height: `calc(${APP_BAR_HEIGHT_BY_THEME[theme]} + ${safeAreaInsetTop})`,
48
+ paddingTop: safeAreaInsetTop,
49
+ };
50
+ }
51
+
52
+ export function getMainLayoutStyle(
53
+ theme: AppBarMainVariantProps["theme"],
54
+ safeAreaInsetTop: string,
55
+ ): AppBarStyleObject | undefined {
56
+ if (theme !== "cupertino") return undefined;
57
+
58
+ return {
59
+ top: safeAreaInsetTop,
60
+ bottom: "0px",
61
+ };
62
+ }
63
+
64
+ export function useAppBar(variantProps: AppBarVariantProps) {
65
+ const { safeAreaInsetTop } = useSafeArea();
66
+ const [leftWidth, setLeftWidth] = React.useState(0);
67
+ const [rightWidth, setRightWidth] = React.useState(0);
68
+ const resolvedTheme = variantProps.theme ?? getDefaultAppBarTheme();
69
+
70
+ const resolvedVariantProps: AppBarVariantProps = {
71
+ ...variantProps,
72
+ theme: resolvedTheme,
73
+ };
74
+ const centeredTitlePaddingX = getCenteredTitlePadding(leftWidth, rightWidth);
75
+ const rootLayoutStyle = getRootLayoutStyle(resolvedTheme, safeAreaInsetTop);
76
+ const sharedVariantProps = React.useMemo<SharedAppBarVariantProps>(
77
+ () => ({
78
+ theme: resolvedVariantProps.theme,
79
+ tone: resolvedVariantProps.tone,
80
+ transitionStyle: resolvedVariantProps.transitionStyle,
81
+ }),
82
+ [resolvedVariantProps.theme, resolvedVariantProps.tone, resolvedVariantProps.transitionStyle],
83
+ );
84
+ const contextValue = React.useMemo<AppBarContextValue>(
85
+ () => ({
86
+ centeredTitlePaddingX,
87
+ safeAreaInsetTop,
88
+ sharedVariantProps,
89
+ setLeftWidth,
90
+ setRightWidth,
91
+ }),
92
+ [centeredTitlePaddingX, safeAreaInsetTop, sharedVariantProps],
93
+ );
94
+
95
+ return {
96
+ contextValue,
97
+ resolvedVariantProps,
98
+ rootLayoutStyle,
99
+ };
100
+ }
@@ -0,0 +1,54 @@
1
+ import "@testing-library/jest-dom";
2
+ import { getQueriesForElement, render } from "@lynx-js/react/testing-library";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { Badge } from "./Badge";
6
+
7
+ function getRenderedQueries() {
8
+ return getQueriesForElement(getRenderedRoot());
9
+ }
10
+
11
+ function getRenderedRoot() {
12
+ const root = elementTree.root;
13
+
14
+ if (!root) {
15
+ throw new Error("Expected Lynx render root to exist.");
16
+ }
17
+
18
+ return root;
19
+ }
20
+
21
+ describe("Badge", () => {
22
+ it("renders label text in the label slot with default variant classes", () => {
23
+ render(<Badge>거래중</Badge>);
24
+
25
+ const { getByText } = getRenderedQueries();
26
+ const root = getRenderedRoot();
27
+ const badgeRoot = root.querySelector(".seed-badge__root");
28
+ const label = getByText("거래중");
29
+
30
+ expect(badgeRoot).toBeInTheDocument();
31
+ expect(badgeRoot).toHaveClass("seed-badge__root--size_medium");
32
+ expect(badgeRoot).toHaveClass("seed-badge__root--variant_solid");
33
+ expect(badgeRoot).toHaveClass("seed-badge__root--tone_neutral");
34
+ expect(label).toHaveClass("seed-badge__label");
35
+ });
36
+
37
+ it("applies variant classes and preserves user className", () => {
38
+ render(
39
+ <Badge className="custom-badge" size="large" variant="outline" tone="brand">
40
+ 추천
41
+ </Badge>,
42
+ );
43
+
44
+ const root = getRenderedRoot();
45
+ const badgeRoot = root.querySelector(".seed-badge__root");
46
+
47
+ expect(badgeRoot).toBeInTheDocument();
48
+ expect(badgeRoot).toHaveClass("custom-badge");
49
+ expect(badgeRoot).toHaveClass("seed-badge__root--size_large");
50
+ expect(badgeRoot).toHaveClass("seed-badge__root--variant_outline");
51
+ expect(badgeRoot).toHaveClass("seed-badge__root--tone_brand");
52
+ expect(badgeRoot).toHaveClass("seed-badge__root--tone_brand-variant_outline");
53
+ });
54
+ });
@@ -0,0 +1,32 @@
1
+ import { badge, type BadgeVariantProps } from "@seed-design/lynx-css/recipes/badge";
2
+ import clsx from "clsx";
3
+ import * as React from "@lynx-js/react";
4
+
5
+ import type { LynxStyledElementProps, LynxViewRef } from "../../types";
6
+
7
+ /**
8
+ * @platform Lynx
9
+ *
10
+ * 웹 Badge와 동일한 variant surface를 제공하지만, Lynx native `<view>` / `<text>`를
11
+ * 직접 렌더링합니다. Badge는 정적 정보 표시 컴포넌트이므로 press/tap API를 별도
12
+ * 소유하지 않습니다.
13
+ */
14
+ export interface BadgeProps extends BadgeVariantProps, LynxStyledElementProps {}
15
+
16
+ export const Badge = React.forwardRef<unknown, BadgeProps>((props, ref) => {
17
+ const [variantProps, otherProps] = badge.splitVariantProps(props);
18
+ const classes = badge(variantProps);
19
+ const { children, className, ...nativeProps } = otherProps;
20
+
21
+ return (
22
+ <view
23
+ {...(ref ? { ref: ref as LynxViewRef } : {})}
24
+ {...nativeProps}
25
+ className={clsx(classes.root, className)}
26
+ >
27
+ <text className={classes.label}>{children}</text>
28
+ </view>
29
+ );
30
+ });
31
+
32
+ Badge.displayName = "Badge";
@@ -0,0 +1 @@
1
+ export { Badge, type BadgeProps } from "./Badge";
@@ -0,0 +1,219 @@
1
+ import { fireEvent, render } from "@lynx-js/react/testing-library";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const sheetMocks = vi.hoisted(() => ({
5
+ contentProps: [] as Array<Record<string, unknown>>,
6
+ rootProps: [] as Array<Record<string, unknown>>,
7
+ rootRef: {
8
+ open: vi.fn(),
9
+ },
10
+ }));
11
+
12
+ vi.mock("@lynx-js/lynx-ui-sheet", async () => {
13
+ const React = await vi.importActual<typeof import("@lynx-js/react")>("@lynx-js/react");
14
+
15
+ const SheetRoot = React.forwardRef<
16
+ unknown,
17
+ Record<string, unknown> & { children?: React.ReactNode }
18
+ >((props, ref) => {
19
+ sheetMocks.rootProps.push(props);
20
+ React.useImperativeHandle(ref, () => sheetMocks.rootRef);
21
+ return <>{props["children"]}</>;
22
+ });
23
+ SheetRoot.displayName = "MockSheetRoot";
24
+
25
+ const SheetView = React.forwardRef<unknown, { children?: React.ReactNode }>((props) => {
26
+ return <>{props.children}</>;
27
+ });
28
+ SheetView.displayName = "MockSheetView";
29
+
30
+ const SheetContent = React.forwardRef<
31
+ unknown,
32
+ Record<string, unknown> & { children?: React.ReactNode }
33
+ >((props) => {
34
+ sheetMocks.contentProps.push(props);
35
+ return <>{props["children"]}</>;
36
+ });
37
+ SheetContent.displayName = "MockSheetContent";
38
+
39
+ const SheetHandle = React.forwardRef<
40
+ unknown,
41
+ Record<string, unknown> & { children?: React.ReactNode }
42
+ >((props) => {
43
+ const children = props["children"] as React.ReactNode;
44
+
45
+ return (
46
+ <view className={props["className"] as string} style={props["style"] as never}>
47
+ {children}
48
+ </view>
49
+ );
50
+ });
51
+ SheetHandle.displayName = "MockSheetHandle";
52
+
53
+ const passthrough = (displayName: string) => {
54
+ const Component = React.forwardRef<unknown, { children?: React.ReactNode }>((props) => {
55
+ return <>{props.children}</>;
56
+ });
57
+ Component.displayName = displayName;
58
+ return Component;
59
+ };
60
+
61
+ return {
62
+ SheetBackdrop: passthrough("MockSheetBackdrop"),
63
+ SheetContent,
64
+ SheetHandle,
65
+ SheetRoot,
66
+ SheetView,
67
+ };
68
+ });
69
+
70
+ vi.mock("../../hooks/useSafeArea", () => ({
71
+ useSafeArea: () => ({
72
+ safeAreaInsetTop: "62px",
73
+ safeAreaInsetBottom: "34px",
74
+ }),
75
+ }));
76
+
77
+ import * as BottomSheet from "./BottomSheet.namespace";
78
+
79
+ describe("BottomSheet", () => {
80
+ beforeEach(() => {
81
+ sheetMocks.contentProps = [];
82
+ sheetMocks.rootProps = [];
83
+ sheetMocks.rootRef.open.mockClear();
84
+ });
85
+
86
+ it("passes handleOnly to the underlying SheetRoot", () => {
87
+ render(
88
+ <BottomSheet.Root handleOnly>
89
+ <BottomSheet.Content />
90
+ </BottomSheet.Root>,
91
+ );
92
+
93
+ expect(sheetMocks.rootProps.at(-1)).toMatchObject({
94
+ handleOnly: true,
95
+ });
96
+ });
97
+
98
+ it("renders Body as a vertical scroll-view", () => {
99
+ const { container } = render(
100
+ <BottomSheet.Root>
101
+ <BottomSheet.Body>
102
+ <text>Scrollable content</text>
103
+ </BottomSheet.Body>
104
+ </BottomSheet.Root>,
105
+ );
106
+
107
+ const body = container.querySelector("scroll-view");
108
+
109
+ expect(body).not.toBeNull();
110
+ expect(body?.hasAttribute("scroll-y")).toBe(true);
111
+ });
112
+
113
+ it("renders Handle with a target-size touch area around the visual handle", () => {
114
+ const { container } = render(
115
+ <BottomSheet.Root>
116
+ <BottomSheet.Handle />
117
+ </BottomSheet.Root>,
118
+ );
119
+
120
+ const touchArea = container.querySelector(".seed-bottom-sheet-handle__touchArea");
121
+ const visualHandle = container.querySelector(".seed-bottom-sheet-handle__root");
122
+
123
+ expect(visualHandle).not.toBeNull();
124
+ expect(touchArea).not.toBeNull();
125
+ expect(touchArea?.contains(visualHandle)).toBe(true);
126
+ });
127
+
128
+ it("passes inner layout styles and safe area bottom to Content", () => {
129
+ render(
130
+ <BottomSheet.Root>
131
+ <BottomSheet.Content innerStyle={{ paddingTop: "8px" }} />
132
+ </BottomSheet.Root>,
133
+ );
134
+
135
+ expect(sheetMocks.contentProps.at(-1)).toMatchObject({
136
+ innerStyle: {
137
+ display: "flex",
138
+ flexDirection: "column",
139
+ minHeight: "0",
140
+ paddingBottom: "34px",
141
+ paddingTop: "8px",
142
+ },
143
+ });
144
+ });
145
+
146
+ it("skips motion-engine animations when Root has skipAnimation", () => {
147
+ render(
148
+ <BottomSheet.Root skipAnimation>
149
+ <BottomSheet.Content />
150
+ </BottomSheet.Root>,
151
+ );
152
+
153
+ expect(sheetMocks.contentProps.at(-1)).toMatchObject({
154
+ snapAnimation: { type: "tween", duration: 0 },
155
+ enterAnimation: { type: "tween", duration: 0 },
156
+ exitAnimation: { type: "tween", duration: 0 },
157
+ });
158
+ });
159
+
160
+ it("preserves user-provided animations when Root has skipAnimation", () => {
161
+ const snapAnimation = { type: "tween", duration: 120 } as const;
162
+ const enterAnimation = { type: "tween", duration: 140 } as const;
163
+ const exitAnimation = { type: "tween", duration: 90 } as const;
164
+
165
+ render(
166
+ <BottomSheet.Root skipAnimation>
167
+ <BottomSheet.Content
168
+ snapAnimation={snapAnimation}
169
+ enterAnimation={enterAnimation}
170
+ exitAnimation={exitAnimation}
171
+ />
172
+ </BottomSheet.Root>,
173
+ );
174
+
175
+ expect(sheetMocks.contentProps.at(-1)).toMatchObject({
176
+ snapAnimation,
177
+ enterAnimation,
178
+ exitAnimation,
179
+ });
180
+ });
181
+
182
+ it("opens with default animation when Root does not skip animation", () => {
183
+ const { getByText } = render(
184
+ <BottomSheet.Root>
185
+ <BottomSheet.Trigger>
186
+ <text>Open sheet</text>
187
+ </BottomSheet.Trigger>
188
+ </BottomSheet.Root>,
189
+ );
190
+
191
+ const trigger = (getByText("Open sheet") as HTMLElement).parentElement;
192
+ if (!trigger) {
193
+ throw new Error("Expected trigger parent element to exist.");
194
+ }
195
+
196
+ fireEvent.tap(trigger);
197
+
198
+ expect(sheetMocks.rootRef.open).toHaveBeenCalledWith();
199
+ });
200
+
201
+ it("opens without animation when Trigger is tapped inside a skipping Root", () => {
202
+ const { getByText } = render(
203
+ <BottomSheet.Root skipAnimation>
204
+ <BottomSheet.Trigger>
205
+ <text>Open sheet</text>
206
+ </BottomSheet.Trigger>
207
+ </BottomSheet.Root>,
208
+ );
209
+
210
+ const trigger = (getByText("Open sheet") as HTMLElement).parentElement;
211
+ if (!trigger) {
212
+ throw new Error("Expected trigger parent element to exist.");
213
+ }
214
+
215
+ fireEvent.tap(trigger);
216
+
217
+ expect(sheetMocks.rootRef.open).toHaveBeenCalledWith({ animate: false });
218
+ });
219
+ });