@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.
- package/lib/_virtual/_rolldown/runtime.js +13 -0
- package/lib/components/ActionButton/ActionButton.d.ts +26 -32
- package/lib/components/ActionButton/ActionButton.d.ts.map +1 -1
- package/lib/components/ActionButton/ActionButton.jsx +139 -120
- package/lib/components/ActionButton/index.d.ts +1 -1
- package/lib/components/AppBar/AppBar.d.ts +35 -0
- package/lib/components/AppBar/AppBar.d.ts.map +1 -0
- package/lib/components/AppBar/AppBar.jsx +132 -0
- package/lib/components/AppBar/AppBar.namespace.d.ts +2 -0
- package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -0
- package/lib/components/AppBar/AppBar.namespace.js +15 -0
- package/lib/components/AppBar/context.d.ts +13 -0
- package/lib/components/AppBar/context.d.ts.map +1 -0
- package/lib/components/AppBar/context.js +12 -0
- package/lib/components/AppBar/index.d.ts +3 -0
- package/lib/components/AppBar/index.d.ts.map +1 -0
- package/lib/components/AppBar/useAppBar.d.ts +15 -0
- package/lib/components/AppBar/useAppBar.d.ts.map +1 -0
- package/lib/components/AppBar/useAppBar.js +72 -0
- package/lib/components/Badge/Badge.d.ts +14 -0
- package/lib/components/Badge/Badge.d.ts.map +1 -0
- package/lib/components/Badge/Badge.jsx +15 -0
- package/lib/components/Badge/index.d.ts +2 -0
- package/lib/components/Badge/index.d.ts.map +1 -0
- package/lib/components/BottomSheet/BottomSheet.d.ts +22 -27
- package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/lib/components/BottomSheet/BottomSheet.jsx +127 -142
- package/lib/components/BottomSheet/BottomSheet.namespace.d.ts +1 -1
- package/lib/components/BottomSheet/BottomSheet.namespace.js +18 -1
- package/lib/components/BottomSheet/index.d.ts +2 -2
- package/lib/components/Box/Box.d.ts +20 -0
- package/lib/components/Box/Box.d.ts.map +1 -0
- package/lib/components/Box/Box.jsx +14 -0
- package/lib/components/Box/index.d.ts +2 -0
- package/lib/components/Box/index.d.ts.map +1 -0
- package/lib/components/Checkbox/Checkbox.d.ts +14 -30
- package/lib/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/lib/components/Checkbox/Checkbox.jsx +107 -98
- package/lib/components/Checkbox/Checkbox.namespace.d.ts +1 -1
- package/lib/components/Checkbox/Checkbox.namespace.js +12 -1
- package/lib/components/Checkbox/index.d.ts +2 -2
- package/lib/components/Icon/Icon.d.ts +37 -0
- package/lib/components/Icon/Icon.d.ts.map +1 -0
- package/lib/components/Icon/Icon.jsx +143 -0
- package/lib/components/Icon/index.d.ts +2 -0
- package/lib/components/Icon/index.d.ts.map +1 -0
- package/lib/components/ProgressCircle/ProgressCircle.d.ts +4 -8
- package/lib/components/ProgressCircle/ProgressCircle.d.ts.map +1 -1
- package/lib/components/ProgressCircle/ProgressCircle.jsx +255 -260
- package/lib/components/ProgressCircle/ProgressCircle.namespace.d.ts +1 -1
- package/lib/components/ProgressCircle/ProgressCircle.namespace.js +9 -1
- package/lib/components/ProgressCircle/index.d.ts +2 -2
- package/lib/components/RadioGroup/RadioGroup.d.ts +12 -28
- package/lib/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/lib/components/RadioGroup/RadioGroup.jsx +134 -118
- package/lib/components/RadioGroup/RadioGroup.namespace.d.ts +1 -1
- package/lib/components/RadioGroup/RadioGroup.namespace.js +12 -1
- package/lib/components/RadioGroup/index.d.ts +2 -2
- package/lib/components/Stack/Stack.d.ts +33 -0
- package/lib/components/Stack/Stack.d.ts.map +1 -0
- package/lib/components/Stack/Stack.jsx +39 -0
- package/lib/components/Stack/index.d.ts +2 -0
- package/lib/components/Stack/index.d.ts.map +1 -0
- package/lib/components/Switch/Switch.d.ts +8 -14
- package/lib/components/Switch/Switch.d.ts.map +1 -1
- package/lib/components/Switch/Switch.jsx +82 -56
- package/lib/components/Switch/Switch.namespace.d.ts +1 -1
- package/lib/components/Switch/Switch.namespace.js +11 -1
- package/lib/components/Switch/index.d.ts +2 -2
- package/lib/components/TagGroup/TagGroup.d.ts +8 -14
- package/lib/components/TagGroup/TagGroup.d.ts.map +1 -1
- package/lib/components/TagGroup/TagGroup.jsx +42 -39
- package/lib/components/TagGroup/TagGroup.namespace.d.ts +1 -1
- package/lib/components/TagGroup/TagGroup.namespace.js +10 -1
- package/lib/components/TagGroup/index.d.ts +2 -2
- package/lib/components/Text/Text.d.ts +7 -0
- package/lib/components/Text/Text.d.ts.map +1 -0
- package/lib/components/Text/Text.jsx +28 -0
- package/lib/components/Text/index.d.ts +2 -0
- package/lib/components/Text/index.d.ts.map +1 -0
- package/lib/components/index.d.ts +13 -7
- package/lib/components/index.d.ts.map +1 -1
- package/lib/hooks/index.d.ts +4 -3
- package/lib/hooks/index.d.ts.map +1 -1
- package/lib/hooks/{use-controllable-state.d.ts → useControllableState.d.ts} +1 -1
- package/lib/hooks/useControllableState.d.ts.map +1 -0
- package/lib/hooks/useControllableState.js +27 -0
- package/lib/hooks/useIconColor.d.ts +19 -0
- package/lib/hooks/useIconColor.d.ts.map +1 -0
- package/lib/hooks/useIconColor.js +38 -0
- package/lib/hooks/{use-press-tap.d.ts → usePressTap.d.ts} +12 -9
- package/lib/hooks/usePressTap.d.ts.map +1 -0
- package/lib/hooks/usePressTap.js +35 -0
- package/lib/hooks/useSafeArea.d.ts +19 -0
- package/lib/hooks/useSafeArea.d.ts.map +1 -0
- package/lib/hooks/useSafeArea.js +33 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +29 -3
- package/lib/types.d.ts +51 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/children.d.ts +3 -0
- package/lib/utils/children.d.ts.map +1 -0
- package/lib/utils/children.js +8 -0
- package/lib/utils/create-slot-recipe-context.d.ts +13 -12
- package/lib/utils/create-slot-recipe-context.d.ts.map +1 -1
- package/lib/utils/create-slot-recipe-context.jsx +88 -70
- package/lib/utils/get-seed-class-name.d.ts +6 -7
- package/lib/utils/get-seed-class-name.d.ts.map +1 -1
- package/lib/utils/get-seed-class-name.js +26 -37
- package/lib/utils/index.d.ts +4 -4
- package/lib/utils/resolve-recipe-token.js +45 -43
- package/lib/utils/split-multiple-variants-props.js +32 -32
- package/lib/utils/styled.d.ts +83 -0
- package/lib/utils/styled.d.ts.map +1 -0
- package/lib/utils/styled.js +144 -0
- package/package.json +9 -9
- package/src/components/ActionButton/ActionButton.test.tsx +167 -0
- package/src/components/ActionButton/ActionButton.tsx +210 -168
- package/src/components/AppBar/AppBar.namespace.ts +18 -0
- package/src/components/AppBar/AppBar.test.tsx +270 -0
- package/src/components/AppBar/AppBar.tsx +288 -0
- package/src/components/AppBar/context.ts +27 -0
- package/src/components/AppBar/index.ts +21 -0
- package/src/components/AppBar/useAppBar.ts +100 -0
- package/src/components/Badge/Badge.test.tsx +54 -0
- package/src/components/Badge/Badge.tsx +32 -0
- package/src/components/Badge/index.ts +1 -0
- package/src/components/BottomSheet/BottomSheet.test.tsx +219 -0
- package/src/components/BottomSheet/BottomSheet.tsx +120 -83
- package/src/components/Box/Box.test.tsx +85 -0
- package/src/components/Box/Box.tsx +39 -0
- package/src/components/Box/index.ts +1 -0
- package/src/components/Checkbox/Checkbox.tsx +108 -117
- package/src/components/Icon/Icon.tsx +265 -0
- package/src/components/Icon/index.ts +8 -0
- package/src/components/ProgressCircle/ProgressCircle.tsx +39 -25
- package/src/components/RadioGroup/RadioGroup.tsx +100 -118
- package/src/components/Stack/Stack.tsx +97 -0
- package/src/components/Stack/index.ts +1 -0
- package/src/components/Switch/Switch.tsx +61 -52
- package/src/components/TagGroup/TagGroup.tsx +27 -25
- package/src/components/Text/Text.tsx +64 -0
- package/src/components/Text/index.ts +1 -0
- package/src/components/index.ts +6 -0
- package/src/hooks/index.ts +4 -3
- package/src/hooks/{__tests__/use-controllable-state.test.ts → useControllableState.test.ts} +1 -1
- package/src/hooks/useIconColor.test.ts +13 -0
- package/src/hooks/useIconColor.ts +67 -0
- package/src/hooks/{__tests__/use-press-tap.test.ts → usePressTap.test.ts} +1 -1
- package/src/hooks/{use-press-tap.ts → usePressTap.ts} +14 -10
- package/src/hooks/useSafeArea.test.ts +83 -0
- package/src/hooks/useSafeArea.ts +56 -0
- package/src/index.ts +1 -0
- package/src/types.ts +76 -0
- package/src/utils/children.ts +13 -0
- package/src/utils/create-slot-recipe-context.test.tsx +96 -0
- package/src/utils/create-slot-recipe-context.tsx +55 -15
- package/src/utils/get-seed-class-name.ts +7 -12
- package/src/utils/{__tests__/split-multiple-variants-props.test.ts → split-multiple-variants-props.test.ts} +1 -1
- package/src/utils/styled.ts +442 -0
- package/lib/components/ActionButton/index.js +0 -1
- package/lib/components/BottomSheet/index.js +0 -2
- package/lib/components/Checkbox/index.js +0 -2
- package/lib/components/ProgressCircle/index.js +0 -2
- package/lib/components/RadioGroup/index.js +0 -2
- package/lib/components/Switch/index.js +0 -2
- package/lib/components/TagGroup/index.js +0 -2
- package/lib/components/index.js +0 -7
- package/lib/hooks/index.js +0 -3
- package/lib/hooks/use-controllable-state.d.ts.map +0 -1
- package/lib/hooks/use-controllable-state.js +0 -27
- package/lib/hooks/use-icon-color.d.ts +0 -15
- package/lib/hooks/use-icon-color.d.ts.map +0 -1
- package/lib/hooks/use-icon-color.js +0 -37
- package/lib/hooks/use-press-tap.d.ts.map +0 -1
- package/lib/hooks/use-press-tap.js +0 -35
- package/lib/utils/index.js +0 -4
- package/src/components/ActionButton/__tests__/ActionButton.test.tsx +0 -35
- package/src/components/BottomSheet/__tests__/BottomSheet.test.tsx +0 -119
- package/src/components/__tests__/ProgressCircle.test.ts +0 -33
- package/src/components/__tests__/exports.test.ts +0 -38
- package/src/components/__tests__/switch.test.ts +0 -34
- package/src/hooks/use-icon-color.ts +0 -51
- package/src/utils/__tests__/create-slot-recipe-context.test.tsx +0 -228
- /package/src/hooks/{use-controllable-state.ts → useControllableState.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useMemo } from "@lynx-js/react";
|
|
2
|
+
|
|
3
|
+
type SafeAreaEdge = "top" | "bottom";
|
|
4
|
+
|
|
5
|
+
declare module "@lynx-js/types" {
|
|
6
|
+
interface GlobalProps {
|
|
7
|
+
safeAreaInsetTop?: number;
|
|
8
|
+
safeAreaInsetBottom?: number;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const safeAreaProp: Record<SafeAreaEdge, "safeAreaInsetTop" | "safeAreaInsetBottom"> = {
|
|
13
|
+
top: "safeAreaInsetTop",
|
|
14
|
+
bottom: "safeAreaInsetBottom",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function getSafeAreaEnvInset(edge: SafeAreaEdge): string {
|
|
18
|
+
return `env(safe-area-inset-${edge})`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeSafeAreaValue(value: number | undefined): string | undefined {
|
|
22
|
+
if (typeof value !== "number" || value <= 0 || !Number.isFinite(value)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `${value}px`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveSafeAreaInset(edge: SafeAreaEdge, value: number | undefined): string {
|
|
30
|
+
return normalizeSafeAreaValue(value) ?? getSafeAreaEnvInset(edge);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UseSafeAreaReturn {
|
|
34
|
+
safeAreaInsetTop: string;
|
|
35
|
+
safeAreaInsetBottom: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lynx 앱에서 top/bottom safe area inset 값을 반환합니다.
|
|
40
|
+
*
|
|
41
|
+
* `lynx.__globalProps.safeAreaInsetTop`과 `safeAreaInsetBottom`을 우선 사용하고,
|
|
42
|
+
* host가 값을 제공하지 않으면 Lynx CSS `env(safe-area-inset-*)` 값을 fallback으로
|
|
43
|
+
* 반환합니다.
|
|
44
|
+
*/
|
|
45
|
+
export function useSafeArea(): UseSafeAreaReturn {
|
|
46
|
+
const safeAreaInsetTop = lynx.__globalProps?.[safeAreaProp.top];
|
|
47
|
+
const safeAreaInsetBottom = lynx.__globalProps?.[safeAreaProp.bottom];
|
|
48
|
+
|
|
49
|
+
return useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
safeAreaInsetTop: resolveSafeAreaInset("top", safeAreaInsetTop),
|
|
52
|
+
safeAreaInsetBottom: resolveSafeAreaInset("bottom", safeAreaInsetBottom),
|
|
53
|
+
}),
|
|
54
|
+
[safeAreaInsetTop, safeAreaInsetBottom],
|
|
55
|
+
);
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ReactNode, Ref } from "@lynx-js/react";
|
|
2
|
+
import type {
|
|
3
|
+
CSSProperties,
|
|
4
|
+
IntrinsicElements,
|
|
5
|
+
MainThread,
|
|
6
|
+
NodesRef,
|
|
7
|
+
TextProps,
|
|
8
|
+
ViewProps,
|
|
9
|
+
} from "@lynx-js/types";
|
|
10
|
+
|
|
11
|
+
export type LynxViewProps = IntrinsicElements["view"] & ViewProps;
|
|
12
|
+
export type LynxTextProps = IntrinsicElements["text"] & TextProps;
|
|
13
|
+
export type LynxViewRef = Ref<NodesRef>;
|
|
14
|
+
export type LynxTextRef = Ref<NodesRef>;
|
|
15
|
+
export type LynxMainThreadRef = Ref<MainThread.Element>;
|
|
16
|
+
export type LynxStyle = CSSProperties;
|
|
17
|
+
|
|
18
|
+
export interface LynxElementProps {
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
className?: LynxViewProps["className"];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LynxStyledElementProps extends LynxElementProps {
|
|
24
|
+
style?: LynxStyle;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LynxPressableProps {
|
|
28
|
+
bindtap?: LynxViewProps["bindtap"];
|
|
29
|
+
"main-thread:bindtap"?: LynxViewProps["main-thread:bindtap"];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LynxTouchProps {
|
|
33
|
+
bindtap?: LynxViewProps["bindtap"];
|
|
34
|
+
bindtouchstart?: LynxViewProps["bindtouchstart"];
|
|
35
|
+
bindtouchend?: LynxViewProps["bindtouchend"];
|
|
36
|
+
bindtouchcancel?: LynxViewProps["bindtouchcancel"];
|
|
37
|
+
"main-thread:bindtap"?: LynxViewProps["main-thread:bindtap"];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LynxIconElementProps {
|
|
41
|
+
className?: IntrinsicElements["image"]["className"];
|
|
42
|
+
style?: LynxStyle;
|
|
43
|
+
ref?: LynxMainThreadRef;
|
|
44
|
+
"main-thread:binduiappear"?: IntrinsicElements["image"]["main-thread:binduiappear"];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @platform Lynx
|
|
49
|
+
*
|
|
50
|
+
* native element(`<view>`/`<text>`/`<image>` 등)가 공유하는 접근성 속성 모음.
|
|
51
|
+
* `@lynx-js/types`의 StandardProps 접근성 속성을 표준화한 타입으로, 컴포넌트가
|
|
52
|
+
* 확장해 native element에 패스스루한다. 모든 native tag가 동일한 공통 속성을
|
|
53
|
+
* 가지며 tag별 차이는 없다.
|
|
54
|
+
*
|
|
55
|
+
* web ARIA 대응:
|
|
56
|
+
* - `accessibility-label` ← `aria-label`
|
|
57
|
+
* - `accessibility-role-description` ← `role` (switch/checkbox/progressbar)
|
|
58
|
+
* - `accessibility-value` ← `aria-checked` / `aria-valuenow`
|
|
59
|
+
* - `accessibility-elements-hidden` ← `aria-hidden`
|
|
60
|
+
*
|
|
61
|
+
* `accessibility-traits`는 단일 값이라 `selected`+`disabled` 동시 표현이 안 된다.
|
|
62
|
+
* 역할은 `accessibility-role-description`, 상태는 `accessibility-value`로 분담한다.
|
|
63
|
+
*/
|
|
64
|
+
export type LynxAccessibilityProps = Pick<
|
|
65
|
+
LynxViewProps,
|
|
66
|
+
| "accessibility-label"
|
|
67
|
+
| "accessibility-traits"
|
|
68
|
+
| "accessibility-element"
|
|
69
|
+
| "accessibility-value"
|
|
70
|
+
| "accessibility-role-description"
|
|
71
|
+
| "accessibility-elements-hidden"
|
|
72
|
+
| "accessibility-heading"
|
|
73
|
+
| "accessibility-actions"
|
|
74
|
+
| "accessibility-exclusive-focus"
|
|
75
|
+
| "ios-platform-accessibility-id"
|
|
76
|
+
>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "@lynx-js/react";
|
|
2
|
+
|
|
3
|
+
export function toArray(children: ReactNode): ReactNode[] {
|
|
4
|
+
if (children == null || typeof children === "boolean") {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(children)) {
|
|
9
|
+
return children.flatMap(toArray);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return [children];
|
|
13
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { forwardRef } from "@lynx-js/react";
|
|
2
|
+
import { render } from "@lynx-js/react/testing-library";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createSlotRecipeContext } from "./create-slot-recipe-context";
|
|
6
|
+
|
|
7
|
+
type MockVariantProps = {
|
|
8
|
+
variant?: "a" | "b";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type MockClassnames = {
|
|
12
|
+
root: string;
|
|
13
|
+
label: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockSlotRecipe = Object.assign(
|
|
17
|
+
(props?: MockVariantProps): MockClassnames => ({
|
|
18
|
+
root: `root-${props?.variant ?? "a"}`,
|
|
19
|
+
label: `label-${props?.variant ?? "a"}`,
|
|
20
|
+
}),
|
|
21
|
+
{
|
|
22
|
+
splitVariantProps: <T extends MockVariantProps>(props: T) => {
|
|
23
|
+
const { variant, ...rest } = props;
|
|
24
|
+
return [{ variant }, rest] as [MockVariantProps, Omit<T, keyof MockVariantProps>];
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
type MockProps = {
|
|
30
|
+
children?: unknown;
|
|
31
|
+
className?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type MockReceived = { props?: MockProps };
|
|
35
|
+
|
|
36
|
+
function createMock(name: string) {
|
|
37
|
+
const received: MockReceived = {};
|
|
38
|
+
const Mock = forwardRef<{ tag: "mock" }, MockProps>((props) => {
|
|
39
|
+
received.props = props;
|
|
40
|
+
return <>{props.children as never}</>;
|
|
41
|
+
});
|
|
42
|
+
Mock.displayName = name;
|
|
43
|
+
return { Mock, received };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("createSlotRecipeContext", () => {
|
|
47
|
+
it("rejects intrinsic native tags to avoid Lynx BackgroundSnapshot errors", () => {
|
|
48
|
+
const { withRootProvider, withProvider, withContext } = createSlotRecipeContext(mockSlotRecipe);
|
|
49
|
+
|
|
50
|
+
expect(() => withRootProvider<MockVariantProps>("view")).toThrow(
|
|
51
|
+
/literal JSX.*BackgroundSnapshot/,
|
|
52
|
+
);
|
|
53
|
+
expect(() => withProvider<unknown, MockVariantProps>("view", "root")).toThrow(
|
|
54
|
+
/literal JSX.*BackgroundSnapshot/,
|
|
55
|
+
);
|
|
56
|
+
expect(() => withContext<unknown, Record<string, unknown>>("text", "label")).toThrow(
|
|
57
|
+
/literal JSX.*BackgroundSnapshot/,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("injects root classnames into context without applying className on the root element", () => {
|
|
62
|
+
const { Mock: Root, received: rootReceived } = createMock("Root");
|
|
63
|
+
const { Mock: Child, received: childReceived } = createMock("Child");
|
|
64
|
+
const { withRootProvider, withContext } = createSlotRecipeContext(mockSlotRecipe);
|
|
65
|
+
|
|
66
|
+
const StyledRoot = withRootProvider<MockVariantProps & MockProps>(Root);
|
|
67
|
+
const StyledLabel = withContext<{ tag: "mock" }, MockProps>(Child, "label");
|
|
68
|
+
|
|
69
|
+
render(
|
|
70
|
+
<StyledRoot variant="b">
|
|
71
|
+
<StyledLabel />
|
|
72
|
+
</StyledRoot>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(rootReceived.props).not.toHaveProperty("className");
|
|
76
|
+
expect(childReceived.props?.className).toBe("label-b");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("applies and merges provider slot classnames", () => {
|
|
80
|
+
const { Mock: Root, received: rootReceived } = createMock("Root");
|
|
81
|
+
const { Mock: Child, received: childReceived } = createMock("Child");
|
|
82
|
+
const { withProvider, withContext } = createSlotRecipeContext(mockSlotRecipe);
|
|
83
|
+
|
|
84
|
+
const StyledRoot = withProvider<{ tag: "mock" }, MockVariantProps & MockProps>(Root, "root");
|
|
85
|
+
const StyledLabel = withContext<{ tag: "mock" }, MockProps>(Child, "label");
|
|
86
|
+
|
|
87
|
+
render(
|
|
88
|
+
<StyledRoot variant="a" className="extra">
|
|
89
|
+
<StyledLabel className="child-extra" />
|
|
90
|
+
</StyledRoot>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(rootReceived.props?.className).toBe("root-a extra");
|
|
94
|
+
expect(childReceived.props?.className).toBe("label-a child-extra");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { createContext, forwardRef, useContext } from "@lynx-js/react";
|
|
2
|
+
import type {
|
|
3
|
+
ElementType,
|
|
4
|
+
ForwardRefExoticComponent,
|
|
5
|
+
PropsWithoutRef,
|
|
6
|
+
ReactElement,
|
|
7
|
+
ReactNode,
|
|
8
|
+
RefAttributes,
|
|
9
|
+
} from "@lynx-js/react";
|
|
2
10
|
import clsx from "clsx";
|
|
3
11
|
|
|
4
12
|
type SlotRecipe<
|
|
@@ -8,24 +16,53 @@ type SlotRecipe<
|
|
|
8
16
|
splitVariantProps: <T extends Props>(props: T) => [Props, Omit<T, keyof Props>];
|
|
9
17
|
};
|
|
10
18
|
|
|
19
|
+
function assertNotIntrinsicComponent(Component: ElementType<any>, caller: string) {
|
|
20
|
+
if (typeof Component !== "string") return;
|
|
21
|
+
|
|
22
|
+
throw new Error(
|
|
23
|
+
`${caller} cannot wrap intrinsic tag "${Component}" in Lynx. Native <view>/<text> slots must be rendered as literal JSX in the component file; passing intrinsic strings through createSlotRecipeContext can cause BackgroundSnapshot not found errors.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
export function createSlotRecipeContext<
|
|
12
28
|
Props extends Record<string, string | boolean | undefined>,
|
|
13
29
|
Classnames extends Record<string, string>,
|
|
14
|
-
>(
|
|
30
|
+
>(
|
|
31
|
+
recipe: SlotRecipe<Props, Classnames>,
|
|
32
|
+
): {
|
|
33
|
+
ClassNamesProvider: (props: { children: ReactNode; value: Classnames }) => ReactElement;
|
|
34
|
+
PropsProvider: (props: { children: ReactNode; value: Props }) => ReactElement;
|
|
35
|
+
useClassNames: () => Classnames;
|
|
36
|
+
useProps: () => Props | null;
|
|
37
|
+
withRootProvider: <P>(
|
|
38
|
+
Component: ElementType<any>,
|
|
39
|
+
options?: {
|
|
40
|
+
defaultProps?: Partial<P>;
|
|
41
|
+
},
|
|
42
|
+
) => ForwardRefExoticComponent<PropsWithoutRef<P>>;
|
|
43
|
+
withProvider: <T, P>(
|
|
44
|
+
Component: ElementType<any>,
|
|
45
|
+
slot: keyof Classnames,
|
|
46
|
+
options?: {
|
|
47
|
+
defaultProps?: Partial<P>;
|
|
48
|
+
},
|
|
49
|
+
) => ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
|
|
50
|
+
withContext: <T, P>(
|
|
51
|
+
Component: ElementType<any>,
|
|
52
|
+
slot?: keyof Classnames,
|
|
53
|
+
options?: {
|
|
54
|
+
defaultProps?: Partial<P>;
|
|
55
|
+
},
|
|
56
|
+
) => ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
|
|
57
|
+
} {
|
|
15
58
|
const ClassNamesContext = createContext<Classnames | null>(null);
|
|
16
59
|
const PropsContext = createContext<Props | null>(null);
|
|
17
60
|
|
|
18
|
-
const ClassNamesProvider = ({
|
|
19
|
-
children,
|
|
20
|
-
value,
|
|
21
|
-
}: {
|
|
22
|
-
children: React.ReactNode;
|
|
23
|
-
value: Classnames;
|
|
24
|
-
}) => {
|
|
61
|
+
const ClassNamesProvider = ({ children, value }: { children: ReactNode; value: Classnames }) => {
|
|
25
62
|
return <ClassNamesContext.Provider value={value}>{children}</ClassNamesContext.Provider>;
|
|
26
63
|
};
|
|
27
64
|
|
|
28
|
-
const PropsProvider = ({ children, value }: { children:
|
|
65
|
+
const PropsProvider = ({ children, value }: { children: ReactNode; value: Props }) => {
|
|
29
66
|
return <PropsContext.Provider value={value}>{children}</PropsContext.Provider>;
|
|
30
67
|
};
|
|
31
68
|
|
|
@@ -45,11 +82,12 @@ export function createSlotRecipeContext<
|
|
|
45
82
|
}
|
|
46
83
|
|
|
47
84
|
const withRootProvider = <P,>(
|
|
48
|
-
Component:
|
|
85
|
+
Component: ElementType<any>,
|
|
49
86
|
options?: {
|
|
50
87
|
defaultProps?: Partial<P>;
|
|
51
88
|
},
|
|
52
|
-
):
|
|
89
|
+
): ForwardRefExoticComponent<PropsWithoutRef<P>> => {
|
|
90
|
+
assertNotIntrinsicComponent(Component, "withRootProvider");
|
|
53
91
|
const { defaultProps } = options ?? {};
|
|
54
92
|
|
|
55
93
|
const StyledComponent = (innerProps: any) => {
|
|
@@ -74,12 +112,13 @@ export function createSlotRecipeContext<
|
|
|
74
112
|
};
|
|
75
113
|
|
|
76
114
|
const withProvider = <T, P>(
|
|
77
|
-
Component:
|
|
115
|
+
Component: ElementType<any>,
|
|
78
116
|
slot: keyof Classnames,
|
|
79
117
|
options?: {
|
|
80
118
|
defaultProps?: Partial<P>;
|
|
81
119
|
},
|
|
82
|
-
):
|
|
120
|
+
): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>> => {
|
|
121
|
+
assertNotIntrinsicComponent(Component, "withProvider");
|
|
83
122
|
const { defaultProps } = options ?? {};
|
|
84
123
|
|
|
85
124
|
const StyledComponent = forwardRef<any, any>((innerProps, ref) => {
|
|
@@ -108,12 +147,13 @@ export function createSlotRecipeContext<
|
|
|
108
147
|
};
|
|
109
148
|
|
|
110
149
|
const withContext = <T, P>(
|
|
111
|
-
Component:
|
|
150
|
+
Component: ElementType<any>,
|
|
112
151
|
slot?: keyof Classnames,
|
|
113
152
|
options?: {
|
|
114
153
|
defaultProps?: Partial<P>;
|
|
115
154
|
},
|
|
116
|
-
):
|
|
155
|
+
): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>> => {
|
|
156
|
+
assertNotIntrinsicComponent(Component, "withContext");
|
|
117
157
|
const { defaultProps } = options ?? {};
|
|
118
158
|
|
|
119
159
|
const StyledComponent = forwardRef<any, any>((innerProps, ref) => {
|
|
@@ -4,17 +4,16 @@ declare const lynx: {
|
|
|
4
4
|
__globalProps?: Record<string, unknown>;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
platform?: "Android" | "iOS" | "Harmony" | "windows" | "macOS";
|
|
9
|
-
};
|
|
7
|
+
export type ColorMode = "system" | "light-only" | "dark-only";
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
export interface GetSeedClassNameOptions {
|
|
10
|
+
colorMode?: ColorMode;
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Lynx 앱의 root `<page>` 요소에 적용할 SEED Design className을 반환한다.
|
|
15
15
|
*
|
|
16
|
-
* 테마(dark/light)
|
|
17
|
-
* 렌더링 전에 올바른 CSS 변수가 적용되도록 한다.
|
|
16
|
+
* 테마(dark/light)를 자동 감지하여 렌더링 전에 올바른 CSS 변수가 적용되도록 한다.
|
|
18
17
|
*
|
|
19
18
|
* @example
|
|
20
19
|
* ```tsx
|
|
@@ -27,7 +26,7 @@ type ColorMode = "system" | "light-only" | "dark-only";
|
|
|
27
26
|
* );
|
|
28
27
|
* ```
|
|
29
28
|
*/
|
|
30
|
-
export function getSeedClassName(options?:
|
|
29
|
+
export function getSeedClassName(options?: GetSeedClassNameOptions): string {
|
|
31
30
|
const { colorMode = "system" } = options ?? {};
|
|
32
31
|
|
|
33
32
|
// Theme — lynx.__globalProps.theme에서 시스템 테마 읽기
|
|
@@ -44,9 +43,5 @@ export function getSeedClassName(options?: { colorMode?: ColorMode }): string {
|
|
|
44
43
|
: "seed-user-color-scheme-light";
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
const platform = SystemInfo?.platform;
|
|
49
|
-
const platformClass = platform === "iOS" ? "seed-platform-ios" : "seed-platform-android";
|
|
50
|
-
|
|
51
|
-
return `${themeClass} ${platformClass}`;
|
|
46
|
+
return themeClass;
|
|
52
47
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { splitMultipleVariantsProps } from "
|
|
2
|
+
import { splitMultipleVariantsProps } from "./split-multiple-variants-props";
|
|
3
3
|
|
|
4
4
|
function makeRecipe<V extends Record<string, unknown>>(variantKeys: (keyof V)[]) {
|
|
5
5
|
return {
|