@0610studio/zs-ui 0.18.0 → 0.19.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/build/ZsUiModule.d.ts.map +1 -1
- package/build/ZsUiModule.js +0 -2
- package/build/ZsUiModule.js.map +1 -1
- package/build/assets/SvgExclamation.d.ts.map +1 -1
- package/build/assets/SvgExclamation.js.map +1 -1
- package/build/assets/SvgTooltipTail.d.ts +7 -0
- package/build/assets/SvgTooltipTail.d.ts.map +1 -0
- package/build/assets/SvgTooltipTail.js +7 -0
- package/build/assets/SvgTooltipTail.js.map +1 -0
- package/build/context/OverlayContext.d.ts.map +1 -1
- package/build/context/OverlayContext.js +11 -14
- package/build/context/OverlayContext.js.map +1 -1
- package/build/context/ThemeContext.d.ts +9 -0
- package/build/context/ThemeContext.d.ts.map +1 -1
- package/build/context/ThemeContext.js +24 -15
- package/build/context/ThemeContext.js.map +1 -1
- package/build/index.d.ts +16 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -7
- package/build/index.js.map +1 -1
- package/build/model/globalOverlay.d.ts +0 -8
- package/build/model/globalOverlay.d.ts.map +1 -1
- package/build/model/globalOverlay.js +0 -9
- package/build/model/globalOverlay.js.map +1 -1
- package/build/model/types.d.ts +2 -1
- package/build/model/types.d.ts.map +1 -1
- package/build/model/types.js.map +1 -1
- package/build/model/useFoldingState.d.ts.map +1 -1
- package/build/model/useFoldingState.js +51 -32
- package/build/model/useFoldingState.js.map +1 -1
- package/build/model/useKeyboard.d.ts.map +1 -1
- package/build/model/useKeyboard.js +8 -4
- package/build/model/useKeyboard.js.map +1 -1
- package/build/model/utils.d.ts +1 -1
- package/build/model/utils.d.ts.map +1 -1
- package/build/model/utils.js +2 -1
- package/build/model/utils.js.map +1 -1
- package/build/overlay/AlertOverlay/index.d.ts +1 -2
- package/build/overlay/AlertOverlay/index.d.ts.map +1 -1
- package/build/overlay/AlertOverlay/index.js +13 -11
- package/build/overlay/AlertOverlay/index.js.map +1 -1
- package/build/overlay/BottomSheetOverlay/index.d.ts.map +1 -1
- package/build/overlay/BottomSheetOverlay/index.js +22 -11
- package/build/overlay/BottomSheetOverlay/index.js.map +1 -1
- package/build/overlay/LoadingNotify/index.d.ts +2 -2
- package/build/overlay/LoadingNotify/index.d.ts.map +1 -1
- package/build/overlay/LoadingNotify/index.js +2 -2
- package/build/overlay/LoadingNotify/index.js.map +1 -1
- package/build/overlay/Modality/index.d.ts.map +1 -1
- package/build/overlay/Modality/index.js +6 -12
- package/build/overlay/Modality/index.js.map +1 -1
- package/build/overlay/PopOver/PopOverMenu.d.ts.map +1 -1
- package/build/overlay/PopOver/PopOverMenu.js +7 -8
- package/build/overlay/PopOver/PopOverMenu.js.map +1 -1
- package/build/overlay/SnackbarNotify/index.d.ts +3 -3
- package/build/overlay/SnackbarNotify/index.d.ts.map +1 -1
- package/build/overlay/SnackbarNotify/index.js +5 -4
- package/build/overlay/SnackbarNotify/index.js.map +1 -1
- package/build/overlay/SnackbarNotify/ui/SnackbarItem.d.ts +4 -4
- package/build/overlay/SnackbarNotify/ui/SnackbarItem.d.ts.map +1 -1
- package/build/overlay/SnackbarNotify/ui/SnackbarItem.js +9 -9
- package/build/overlay/SnackbarNotify/ui/SnackbarItem.js.map +1 -1
- package/build/overlay/ZSPortal/index.d.ts.map +1 -1
- package/build/overlay/ZSPortal/index.js +15 -4
- package/build/overlay/ZSPortal/index.js.map +1 -1
- package/build/overlay/ui/ModalBackground.js.map +1 -1
- package/build/theme/elevation.d.ts +94 -10
- package/build/theme/elevation.d.ts.map +1 -1
- package/build/theme/elevation.js +39 -35
- package/build/theme/elevation.js.map +1 -1
- package/build/theme/intentColors.d.ts +9 -0
- package/build/theme/intentColors.d.ts.map +1 -0
- package/build/theme/intentColors.js +30 -0
- package/build/theme/intentColors.js.map +1 -0
- package/build/theme/palette.d.ts +1 -5
- package/build/theme/palette.d.ts.map +1 -1
- package/build/theme/palette.js +3 -12
- package/build/theme/palette.js.map +1 -1
- package/build/theme/resolveColor.d.ts +4 -0
- package/build/theme/resolveColor.d.ts.map +1 -0
- package/build/theme/resolveColor.js +29 -0
- package/build/theme/resolveColor.js.map +1 -0
- package/build/theme/tokens.d.ts +35 -0
- package/build/theme/tokens.d.ts.map +1 -0
- package/build/theme/tokens.js +35 -0
- package/build/theme/tokens.js.map +1 -0
- package/build/theme/typography.d.ts.map +1 -1
- package/build/theme/typography.js +36 -72
- package/build/theme/typography.js.map +1 -1
- package/build/ui/ZSBlockButton/index.d.ts +4 -2
- package/build/ui/ZSBlockButton/index.d.ts.map +1 -1
- package/build/ui/ZSBlockButton/index.js +17 -80
- package/build/ui/ZSBlockButton/index.js.map +1 -1
- package/build/ui/ZSBorderBeam/index.d.ts +48 -0
- package/build/ui/ZSBorderBeam/index.d.ts.map +1 -0
- package/build/ui/ZSBorderBeam/index.js +135 -0
- package/build/ui/ZSBorderBeam/index.js.map +1 -0
- package/build/ui/ZSCheckBox/index.d.ts +18 -0
- package/build/ui/ZSCheckBox/index.d.ts.map +1 -0
- package/build/ui/ZSCheckBox/index.js +60 -0
- package/build/ui/ZSCheckBox/index.js.map +1 -0
- package/build/ui/ZSChip/index.d.ts +28 -0
- package/build/ui/ZSChip/index.d.ts.map +1 -0
- package/build/ui/ZSChip/index.js +76 -0
- package/build/ui/ZSChip/index.js.map +1 -0
- package/build/ui/ZSContainer/index.d.ts +19 -0
- package/build/ui/ZSContainer/index.d.ts.map +1 -1
- package/build/ui/ZSContainer/index.js +29 -15
- package/build/ui/ZSContainer/index.js.map +1 -1
- package/build/ui/ZSPressable/index.d.ts.map +1 -1
- package/build/ui/ZSPressable/index.js +9 -6
- package/build/ui/ZSPressable/index.js.map +1 -1
- package/build/ui/ZSRadioGroup/index.d.ts +4 -4
- package/build/ui/ZSRadioGroup/index.d.ts.map +1 -1
- package/build/ui/ZSRadioGroup/index.js +72 -52
- package/build/ui/ZSRadioGroup/index.js.map +1 -1
- package/build/ui/ZSSegmented/index.d.ts +26 -0
- package/build/ui/ZSSegmented/index.d.ts.map +1 -0
- package/build/ui/ZSSegmented/index.js +124 -0
- package/build/ui/ZSSegmented/index.js.map +1 -0
- package/build/ui/ZSSkeleton/SkiaShimmer.d.ts +18 -0
- package/build/ui/ZSSkeleton/SkiaShimmer.d.ts.map +1 -0
- package/build/ui/ZSSkeleton/SkiaShimmer.js +73 -0
- package/build/ui/ZSSkeleton/SkiaShimmer.js.map +1 -0
- package/build/ui/ZSSkeleton/index.d.ts +9 -3
- package/build/ui/ZSSkeleton/index.d.ts.map +1 -1
- package/build/ui/ZSSkeleton/index.js +13 -44
- package/build/ui/ZSSkeleton/index.js.map +1 -1
- package/build/ui/ZSSkeletonBox/index.d.ts +10 -3
- package/build/ui/ZSSkeletonBox/index.d.ts.map +1 -1
- package/build/ui/ZSSkeletonBox/index.js +9 -47
- package/build/ui/ZSSkeletonBox/index.js.map +1 -1
- package/build/ui/ZSSwitch/index.d.ts +4 -2
- package/build/ui/ZSSwitch/index.d.ts.map +1 -1
- package/build/ui/ZSSwitch/index.js +8 -6
- package/build/ui/ZSSwitch/index.js.map +1 -1
- package/build/ui/ZSText/index.d.ts +3 -2
- package/build/ui/ZSText/index.d.ts.map +1 -1
- package/build/ui/ZSText/index.js +5 -16
- package/build/ui/ZSText/index.js.map +1 -1
- package/build/ui/ZSTextField/index.d.ts.map +1 -1
- package/build/ui/ZSTextField/index.js +57 -40
- package/build/ui/ZSTextField/index.js.map +1 -1
- package/build/ui/ZSTextField/ui/ButtonClose.d.ts.map +1 -1
- package/build/ui/ZSTextField/ui/ButtonClose.js +4 -1
- package/build/ui/ZSTextField/ui/ButtonClose.js.map +1 -1
- package/build/ui/ZSTooltip/index.d.ts +38 -0
- package/build/ui/ZSTooltip/index.d.ts.map +1 -0
- package/build/ui/ZSTooltip/index.js +99 -0
- package/build/ui/ZSTooltip/index.js.map +1 -0
- package/build/ui/ZSView/index.d.ts +3 -2
- package/build/ui/ZSView/index.d.ts.map +1 -1
- package/build/ui/ZSView/index.js +7 -8
- package/build/ui/ZSView/index.js.map +1 -1
- package/build/ui/atoms/AnimatedWrapper.d.ts.map +1 -1
- package/build/ui/atoms/AnimatedWrapper.js +33 -40
- package/build/ui/atoms/AnimatedWrapper.js.map +1 -1
- package/build/ui/atoms/TextAtom.d.ts +1 -2
- package/build/ui/atoms/TextAtom.d.ts.map +1 -1
- package/build/ui/atoms/TextAtom.js.map +1 -1
- package/build/ui/atoms/ViewAtom.d.ts +1 -2
- package/build/ui/atoms/ViewAtom.d.ts.map +1 -1
- package/build/ui/atoms/ViewAtom.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { Blur, Canvas, Group, RoundedRect, SweepGradient, vec } from "@shopify/react-native-skia";
|
|
5
|
+
import { Easing, cancelAnimation, interpolate, useDerivedValue, useSharedValue, withDelay, withRepeat, withTiming, } from "react-native-reanimated";
|
|
6
|
+
import { useTheme } from "../../context/ThemeContext";
|
|
7
|
+
const FULL_ROTATION_RADIANS = Math.PI * 2;
|
|
8
|
+
const DEFAULT_GLOW_INTENSITY = 0.5;
|
|
9
|
+
const GLOW_PULSE_DURATION = 2400;
|
|
10
|
+
/** #RGB / #RRGGBB hex에만 알파를 붙이고, 그 외 형식은 null 반환 */
|
|
11
|
+
const withAlpha = (color, alphaHex) => {
|
|
12
|
+
if (/^#[0-9a-fA-F]{6}$/.test(color))
|
|
13
|
+
return `${color}${alphaHex}`;
|
|
14
|
+
if (/^#[0-9a-fA-F]{3}$/.test(color)) {
|
|
15
|
+
const [r, g, b] = color.slice(1);
|
|
16
|
+
return `#${r}${r}${g}${g}${b}${b}${alphaHex}`;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
/** 색상의 완전 투명 버전 (hue 유지 → blur 시 색 번짐이 자연스러움) */
|
|
21
|
+
const transparentize = (color) => withAlpha(color, "00") ?? "#00000000";
|
|
22
|
+
const resolveGlow = (glow) => {
|
|
23
|
+
if (glow === false || glow === 0)
|
|
24
|
+
return null;
|
|
25
|
+
const intensity = typeof glow === "number" ? Math.min(Math.max(glow, 0), 1) : DEFAULT_GLOW_INTENSITY;
|
|
26
|
+
const maxOpacity = 0.3 + 0.6 * intensity;
|
|
27
|
+
const scaled = {
|
|
28
|
+
width: 16 * intensity,
|
|
29
|
+
blur: 20 * intensity,
|
|
30
|
+
pulseDuration: GLOW_PULSE_DURATION,
|
|
31
|
+
minOpacity: maxOpacity * 0.58,
|
|
32
|
+
maxOpacity,
|
|
33
|
+
};
|
|
34
|
+
if (typeof glow === "object") {
|
|
35
|
+
return {
|
|
36
|
+
width: glow.width ?? scaled.width,
|
|
37
|
+
blur: glow.blur ?? scaled.blur,
|
|
38
|
+
pulseDuration: glow.pulseDuration ?? scaled.pulseDuration,
|
|
39
|
+
minOpacity: glow.minOpacity ?? scaled.minOpacity,
|
|
40
|
+
maxOpacity: glow.maxOpacity ?? scaled.maxOpacity,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return scaled;
|
|
44
|
+
};
|
|
45
|
+
function ZSBorderBeam({ children, colors, colorFrom, colorTo, beamLength = 0.35, trackColor, glow = true, duration = 5000, delay = 0, reverse = false, active = true, borderWidth = 2, borderRadius = 14, style, ...props }) {
|
|
46
|
+
const { palette } = useTheme();
|
|
47
|
+
const [layout, setLayout] = useState({ width: 0, height: 0 });
|
|
48
|
+
const rotationProgress = useSharedValue(0);
|
|
49
|
+
const glowProgress = useSharedValue(0);
|
|
50
|
+
const from = colorFrom || palette.primary.main;
|
|
51
|
+
const to = colorTo || palette.secondary.main;
|
|
52
|
+
const glowSettings = resolveGlow(glow);
|
|
53
|
+
// 혜성 형태: 꼬리(투명)에서 머리(선명)로 갈수록 밝아지고, 머리 직후 짧게 컷오프.
|
|
54
|
+
// beamLength < 1이면 positions로 광선을 둘레 일부에만 압축한다.
|
|
55
|
+
const beam = useMemo(() => {
|
|
56
|
+
const length = Math.min(Math.max(beamLength, 0.05), 1);
|
|
57
|
+
const base = colors && colors.length >= 2
|
|
58
|
+
? colors
|
|
59
|
+
: [transparentize(from), withAlpha(from, "66") ?? from, from, to];
|
|
60
|
+
if (length >= 1) {
|
|
61
|
+
const ring = colors && colors.length >= 2 ? colors : [...base, transparentize(to)];
|
|
62
|
+
return { colors: ring, positions: undefined };
|
|
63
|
+
}
|
|
64
|
+
const head = base[base.length - 1] ?? to;
|
|
65
|
+
const step = length / (base.length - 1);
|
|
66
|
+
return {
|
|
67
|
+
colors: [...base, transparentize(head), transparentize(head)],
|
|
68
|
+
positions: [...base.map((_, index) => index * step), Math.min(length + 0.02, 1), 1],
|
|
69
|
+
};
|
|
70
|
+
}, [colors, from, to, beamLength]);
|
|
71
|
+
const resolvedTrackColor = trackColor === "none" ? null : trackColor ?? withAlpha(from, "1F") ?? "#8080801F";
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!active)
|
|
74
|
+
return;
|
|
75
|
+
rotationProgress.value = 0;
|
|
76
|
+
rotationProgress.value = withDelay(delay, withRepeat(withTiming(1, { duration, easing: Easing.linear }), -1, false));
|
|
77
|
+
return () => cancelAnimation(rotationProgress);
|
|
78
|
+
}, [active, duration, delay, reverse]);
|
|
79
|
+
const glowPulseDuration = glowSettings?.pulseDuration;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!active || !glowPulseDuration)
|
|
82
|
+
return;
|
|
83
|
+
glowProgress.value = 0;
|
|
84
|
+
glowProgress.value = withDelay(delay, withRepeat(withTiming(1, { duration: glowPulseDuration, easing: Easing.inOut(Easing.ease) }), -1, true));
|
|
85
|
+
return () => cancelAnimation(glowProgress);
|
|
86
|
+
}, [active, delay, glowPulseDuration]);
|
|
87
|
+
const handleLayout = (event) => {
|
|
88
|
+
const { width, height } = event.nativeEvent.layout;
|
|
89
|
+
setLayout((prev) => (prev.width === width && prev.height === height ? prev : { width, height }));
|
|
90
|
+
};
|
|
91
|
+
const glowPadding = glowSettings ? Math.ceil(glowSettings.blur + glowSettings.width) : 0;
|
|
92
|
+
const canvasWidth = layout.width + glowPadding * 2;
|
|
93
|
+
const canvasHeight = layout.height + glowPadding * 2;
|
|
94
|
+
const hasLayout = layout.width > 0 && layout.height > 0;
|
|
95
|
+
const frameX = glowPadding + borderWidth / 2;
|
|
96
|
+
const frameY = glowPadding + borderWidth / 2;
|
|
97
|
+
const frameWidth = Math.max(layout.width - borderWidth, 0);
|
|
98
|
+
const frameHeight = Math.max(layout.height - borderWidth, 0);
|
|
99
|
+
const frameRadius = Math.min(Math.max(borderRadius - borderWidth / 2, 0), Math.min(frameWidth, frameHeight) / 2);
|
|
100
|
+
const centerVector = useMemo(() => vec(canvasWidth / 2, canvasHeight / 2), [canvasWidth, canvasHeight]);
|
|
101
|
+
const direction = reverse ? -1 : 1;
|
|
102
|
+
const glowMinOpacity = glowSettings?.minOpacity ?? 0;
|
|
103
|
+
const glowMaxOpacity = glowSettings?.maxOpacity ?? 0;
|
|
104
|
+
const glowWidth = glowSettings?.width ?? 0;
|
|
105
|
+
const gradientTransform = useDerivedValue(() => [
|
|
106
|
+
{ rotate: direction * FULL_ROTATION_RADIANS * rotationProgress.value },
|
|
107
|
+
]);
|
|
108
|
+
const glowOpacity = useDerivedValue(() => interpolate(glowProgress.value, [0, 1], [glowMinOpacity, glowMaxOpacity]));
|
|
109
|
+
const glowStrokeWidth = useDerivedValue(() => interpolate(glowProgress.value, [0, 1], [borderWidth + glowWidth * 0.6, borderWidth + glowWidth]));
|
|
110
|
+
const beamGradient = (_jsx(SweepGradient, { origin: centerVector, c: centerVector, colors: beam.colors, positions: beam.positions, transform: gradientTransform }));
|
|
111
|
+
return (_jsxs(View, { style: [styles.container, style], ...props, children: [active && hasLayout && (_jsxs(Canvas, { pointerEvents: "none", style: [
|
|
112
|
+
styles.canvas,
|
|
113
|
+
{
|
|
114
|
+
width: canvasWidth,
|
|
115
|
+
height: canvasHeight,
|
|
116
|
+
top: -glowPadding,
|
|
117
|
+
left: -glowPadding,
|
|
118
|
+
},
|
|
119
|
+
], children: [resolvedTrackColor && (_jsx(RoundedRect, { x: frameX, y: frameY, width: frameWidth, height: frameHeight, r: frameRadius, style: "stroke", strokeWidth: borderWidth, color: resolvedTrackColor })), glowSettings && (_jsxs(Group, { children: [_jsx(RoundedRect, { x: frameX, y: frameY, width: frameWidth, height: frameHeight, r: frameRadius, style: "stroke", strokeWidth: glowStrokeWidth, opacity: glowOpacity, children: beamGradient }), _jsx(Blur, { blur: glowSettings.blur })] })), _jsx(RoundedRect, { x: frameX, y: frameY, width: frameWidth, height: frameHeight, r: frameRadius, style: "stroke", strokeWidth: borderWidth, children: beamGradient })] })), _jsx(View, { onLayout: handleLayout, style: [styles.content, { borderRadius }], children: children })] }));
|
|
120
|
+
}
|
|
121
|
+
export default React.memo(ZSBorderBeam);
|
|
122
|
+
const styles = StyleSheet.create({
|
|
123
|
+
container: {
|
|
124
|
+
alignSelf: "stretch",
|
|
125
|
+
position: "relative",
|
|
126
|
+
},
|
|
127
|
+
canvas: {
|
|
128
|
+
position: "absolute",
|
|
129
|
+
},
|
|
130
|
+
content: {
|
|
131
|
+
position: "relative",
|
|
132
|
+
overflow: "hidden",
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSBorderBeam/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { StyleSheet, View, type LayoutChangeEvent, type StyleProp, type ViewProps, type ViewStyle } from \"react-native\";\nimport { Blur, Canvas, Group, RoundedRect, SweepGradient, vec } from \"@shopify/react-native-skia\";\nimport {\n Easing,\n cancelAnimation,\n interpolate,\n useDerivedValue,\n useSharedValue,\n withDelay,\n withRepeat,\n withTiming,\n} from \"react-native-reanimated\";\nimport { useTheme } from \"../../context/ThemeContext\";\n\nconst FULL_ROTATION_RADIANS = Math.PI * 2;\nconst DEFAULT_GLOW_INTENSITY = 0.5;\nconst GLOW_PULSE_DURATION = 2400;\n\n/** glow를 세밀하게 제어할 때 사용하는 설정 (숫자 하나로 충분하면 glow={0~1} 사용) */\nexport interface ZSBorderBeamGlowConfig {\n /** 글로우가 stroke 바깥으로 퍼지는 폭(px) */\n width?: number;\n /** blur 강도 */\n blur?: number;\n /** 숨쉬는 한 사이클(ms) */\n pulseDuration?: number;\n minOpacity?: number;\n maxOpacity?: number;\n}\n\nexport interface ZSBorderBeamProps extends ViewProps {\n children?: React.ReactNode;\n /** sweep gradient 색상 배열 (꼬리 → 머리 순) — 지정 시 colorFrom/colorTo는 무시됩니다 */\n colors?: string[];\n /** 광선 시작(꼬리) 색상 (기본: palette.primary.main) */\n colorFrom?: string;\n /** 광선 끝(머리) 색상 (기본: palette.secondary.main) */\n colorTo?: string;\n /** 광선(혜성)이 둘레에서 차지하는 비율 (0~1). 1이면 둘레 전체 그라디언트 링 */\n beamLength?: number;\n /** 광선이 지나가지 않는 구간의 얇은 기본 테두리 색. 'none'이면 표시 안 함 (기본: colorFrom 12% 알파) */\n trackColor?: string;\n /**\n * 글로우 강도.\n * - `false` : 글로우 없음 (선명한 광선만)\n * - `0~1` : 숫자 하나로 폭·blur·불투명도를 함께 스케일 (기본 0.5)\n * - config : 세밀 제어 — { width, blur, pulseDuration, minOpacity, maxOpacity }\n */\n glow?: boolean | number | ZSBorderBeamGlowConfig;\n /** 광선이 한 바퀴 도는 시간(ms) */\n duration?: number;\n /** 애니메이션 시작 지연(ms) */\n delay?: number;\n /** 회전 방향 반전 */\n reverse?: boolean;\n /** false면 효과를 숨기고 애니메이션을 정지 */\n active?: boolean;\n borderWidth?: number;\n borderRadius?: number;\n style?: StyleProp<ViewStyle>;\n}\n\n/** #RGB / #RRGGBB hex에만 알파를 붙이고, 그 외 형식은 null 반환 */\nconst withAlpha = (color: string, alphaHex: string): string | null => {\n if (/^#[0-9a-fA-F]{6}$/.test(color)) return `${color}${alphaHex}`;\n if (/^#[0-9a-fA-F]{3}$/.test(color)) {\n const [r, g, b] = color.slice(1);\n return `#${r}${r}${g}${g}${b}${b}${alphaHex}`;\n }\n return null;\n};\n\n/** 색상의 완전 투명 버전 (hue 유지 → blur 시 색 번짐이 자연스러움) */\nconst transparentize = (color: string): string => withAlpha(color, \"00\") ?? \"#00000000\";\n\ninterface GlowSettings {\n width: number;\n blur: number;\n pulseDuration: number;\n minOpacity: number;\n maxOpacity: number;\n}\n\nconst resolveGlow = (glow: ZSBorderBeamProps[\"glow\"]): GlowSettings | null => {\n if (glow === false || glow === 0) return null;\n\n const intensity = typeof glow === \"number\" ? Math.min(Math.max(glow, 0), 1) : DEFAULT_GLOW_INTENSITY;\n const maxOpacity = 0.3 + 0.6 * intensity;\n const scaled: GlowSettings = {\n width: 16 * intensity,\n blur: 20 * intensity,\n pulseDuration: GLOW_PULSE_DURATION,\n minOpacity: maxOpacity * 0.58,\n maxOpacity,\n };\n\n if (typeof glow === \"object\") {\n return {\n width: glow.width ?? scaled.width,\n blur: glow.blur ?? scaled.blur,\n pulseDuration: glow.pulseDuration ?? scaled.pulseDuration,\n minOpacity: glow.minOpacity ?? scaled.minOpacity,\n maxOpacity: glow.maxOpacity ?? scaled.maxOpacity,\n };\n }\n return scaled;\n};\n\nfunction ZSBorderBeam({\n children,\n colors,\n colorFrom,\n colorTo,\n beamLength = 0.35,\n trackColor,\n glow = true,\n duration = 5000,\n delay = 0,\n reverse = false,\n active = true,\n borderWidth = 2,\n borderRadius = 14,\n style,\n ...props\n}: ZSBorderBeamProps) {\n const { palette } = useTheme();\n const [layout, setLayout] = useState({ width: 0, height: 0 });\n const rotationProgress = useSharedValue(0);\n const glowProgress = useSharedValue(0);\n\n const from = colorFrom || palette.primary.main;\n const to = colorTo || palette.secondary.main;\n const glowSettings = resolveGlow(glow);\n\n // 혜성 형태: 꼬리(투명)에서 머리(선명)로 갈수록 밝아지고, 머리 직후 짧게 컷오프.\n // beamLength < 1이면 positions로 광선을 둘레 일부에만 압축한다.\n const beam = useMemo(() => {\n const length = Math.min(Math.max(beamLength, 0.05), 1);\n const base =\n colors && colors.length >= 2\n ? colors\n : [transparentize(from), withAlpha(from, \"66\") ?? from, from, to];\n\n if (length >= 1) {\n const ring = colors && colors.length >= 2 ? colors : [...base, transparentize(to)];\n return { colors: ring, positions: undefined };\n }\n\n const head = base[base.length - 1] ?? to;\n const step = length / (base.length - 1);\n return {\n colors: [...base, transparentize(head), transparentize(head)],\n positions: [...base.map((_, index) => index * step), Math.min(length + 0.02, 1), 1],\n };\n }, [colors, from, to, beamLength]);\n\n const resolvedTrackColor =\n trackColor === \"none\" ? null : trackColor ?? withAlpha(from, \"1F\") ?? \"#8080801F\";\n\n useEffect(() => {\n if (!active) return;\n\n rotationProgress.value = 0;\n rotationProgress.value = withDelay(\n delay,\n withRepeat(withTiming(1, { duration, easing: Easing.linear }), -1, false)\n );\n\n return () => cancelAnimation(rotationProgress);\n }, [active, duration, delay, reverse]);\n\n const glowPulseDuration = glowSettings?.pulseDuration;\n useEffect(() => {\n if (!active || !glowPulseDuration) return;\n\n glowProgress.value = 0;\n glowProgress.value = withDelay(\n delay,\n withRepeat(withTiming(1, { duration: glowPulseDuration, easing: Easing.inOut(Easing.ease) }), -1, true)\n );\n\n return () => cancelAnimation(glowProgress);\n }, [active, delay, glowPulseDuration]);\n\n const handleLayout = (event: LayoutChangeEvent) => {\n const { width, height } = event.nativeEvent.layout;\n setLayout((prev) => (prev.width === width && prev.height === height ? prev : { width, height }));\n };\n\n const glowPadding = glowSettings ? Math.ceil(glowSettings.blur + glowSettings.width) : 0;\n const canvasWidth = layout.width + glowPadding * 2;\n const canvasHeight = layout.height + glowPadding * 2;\n const hasLayout = layout.width > 0 && layout.height > 0;\n\n const frameX = glowPadding + borderWidth / 2;\n const frameY = glowPadding + borderWidth / 2;\n const frameWidth = Math.max(layout.width - borderWidth, 0);\n const frameHeight = Math.max(layout.height - borderWidth, 0);\n const frameRadius = Math.min(Math.max(borderRadius - borderWidth / 2, 0), Math.min(frameWidth, frameHeight) / 2);\n\n const centerVector = useMemo(() => vec(canvasWidth / 2, canvasHeight / 2), [canvasWidth, canvasHeight]);\n const direction = reverse ? -1 : 1;\n\n const glowMinOpacity = glowSettings?.minOpacity ?? 0;\n const glowMaxOpacity = glowSettings?.maxOpacity ?? 0;\n const glowWidth = glowSettings?.width ?? 0;\n\n const gradientTransform = useDerivedValue(() => [\n { rotate: direction * FULL_ROTATION_RADIANS * rotationProgress.value },\n ]);\n const glowOpacity = useDerivedValue(() =>\n interpolate(glowProgress.value, [0, 1], [glowMinOpacity, glowMaxOpacity])\n );\n const glowStrokeWidth = useDerivedValue(() =>\n interpolate(glowProgress.value, [0, 1], [borderWidth + glowWidth * 0.6, borderWidth + glowWidth])\n );\n\n const beamGradient = (\n <SweepGradient\n origin={centerVector}\n c={centerVector}\n colors={beam.colors}\n positions={beam.positions}\n transform={gradientTransform}\n />\n );\n\n return (\n <View style={[styles.container, style]} {...props}>\n {active && hasLayout && (\n <Canvas\n pointerEvents=\"none\"\n style={[\n styles.canvas,\n {\n width: canvasWidth,\n height: canvasHeight,\n top: -glowPadding,\n left: -glowPadding,\n },\n ]}\n >\n {resolvedTrackColor && (\n <RoundedRect\n x={frameX}\n y={frameY}\n width={frameWidth}\n height={frameHeight}\n r={frameRadius}\n style=\"stroke\"\n strokeWidth={borderWidth}\n color={resolvedTrackColor}\n />\n )}\n\n {glowSettings && (\n <Group>\n <RoundedRect\n x={frameX}\n y={frameY}\n width={frameWidth}\n height={frameHeight}\n r={frameRadius}\n style=\"stroke\"\n strokeWidth={glowStrokeWidth}\n opacity={glowOpacity}\n >\n {beamGradient}\n </RoundedRect>\n <Blur blur={glowSettings.blur} />\n </Group>\n )}\n\n <RoundedRect\n x={frameX}\n y={frameY}\n width={frameWidth}\n height={frameHeight}\n r={frameRadius}\n style=\"stroke\"\n strokeWidth={borderWidth}\n >\n {beamGradient}\n </RoundedRect>\n </Canvas>\n )}\n\n <View onLayout={handleLayout} style={[styles.content, { borderRadius }]}>\n {children}\n </View>\n </View>\n );\n}\n\nexport default React.memo(ZSBorderBeam);\n\nconst styles = StyleSheet.create({\n container: {\n alignSelf: \"stretch\",\n position: \"relative\",\n },\n canvas: {\n position: \"absolute\",\n },\n content: {\n position: \"relative\",\n overflow: \"hidden\",\n },\n});\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewProps } from 'react-native';
|
|
3
|
+
import { ZSTextProps } from '../ZSText';
|
|
4
|
+
export interface ZSCheckBoxProps extends ViewProps {
|
|
5
|
+
value: boolean;
|
|
6
|
+
onChange: (value: boolean) => void;
|
|
7
|
+
size?: number;
|
|
8
|
+
activeColor?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
labelStyle?: ZSTextProps;
|
|
11
|
+
labelComponent?: React.ReactNode;
|
|
12
|
+
moreComponent?: React.ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function ZSCheckBox({ value, onChange, size, activeColor, label, labelStyle, labelComponent, moreComponent, disabled, style, ...props }: ZSCheckBoxProps): React.JSX.Element;
|
|
16
|
+
declare const _default: React.MemoExoticComponent<typeof ZSCheckBox>;
|
|
17
|
+
export default _default;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSCheckBox/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAyB,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAShD,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACjC,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iBAAS,UAAU,CAAC,EAClB,KAAK,EACL,QAAQ,EACR,IAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,QAAgB,EAChB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,eAAe,qBA2CjB;;AA8BD,wBAAsC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
import { Pressable, StyleSheet } from 'react-native';
|
|
4
|
+
import ViewAtom from '../atoms/ViewAtom';
|
|
5
|
+
import ZSText from '../ZSText';
|
|
6
|
+
import { useTheme } from '../../context/ThemeContext';
|
|
7
|
+
import { DISABLED_OPACITY, RADIUS } from '../../theme/tokens';
|
|
8
|
+
import { SvgCheck } from '../../assets/SvgCheck';
|
|
9
|
+
// size 기본값(18) 기준 곡률·보더 비율 유지용
|
|
10
|
+
const BASE_SIZE = 18;
|
|
11
|
+
const BASE_BORDER_WIDTH = 1.6;
|
|
12
|
+
function ZSCheckBox({ value, onChange, size = 18, activeColor, label, labelStyle, labelComponent, moreComponent, disabled = false, style, ...props }) {
|
|
13
|
+
const { palette } = useTheme();
|
|
14
|
+
const checkedColor = activeColor ?? palette.primary.main;
|
|
15
|
+
const handlePress = useCallback(() => {
|
|
16
|
+
if (!disabled) {
|
|
17
|
+
onChange(!value);
|
|
18
|
+
}
|
|
19
|
+
}, [disabled, onChange, value]);
|
|
20
|
+
return (_jsxs(ViewAtom, { style: [styles.container, disabled && styles.disabled, style], ...props, children: [_jsxs(Pressable, { onPress: handlePress, disabled: disabled, style: styles.checkContainer, accessibilityRole: 'checkbox', accessibilityState: { checked: value, disabled }, children: [_jsx(ViewAtom, { style: [
|
|
21
|
+
styles.box,
|
|
22
|
+
{
|
|
23
|
+
width: size,
|
|
24
|
+
height: size,
|
|
25
|
+
borderRadius: RADIUS.xs * (size / BASE_SIZE),
|
|
26
|
+
borderWidth: BASE_BORDER_WIDTH * (size / BASE_SIZE),
|
|
27
|
+
backgroundColor: value ? checkedColor : 'transparent',
|
|
28
|
+
borderColor: value ? checkedColor : palette.grey[30],
|
|
29
|
+
},
|
|
30
|
+
], children: value && (_jsx(ViewAtom, { style: styles.checkmark, children: _jsx(SvgCheck, { size: size - 4, strokeWidth: "3.5" }) })) }), labelComponent ?? (label !== undefined && _jsx(ZSText, { ...labelStyle, children: label }))] }), moreComponent] }));
|
|
31
|
+
}
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
container: {
|
|
34
|
+
width: '100%',
|
|
35
|
+
flexDirection: 'row',
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'space-between',
|
|
38
|
+
},
|
|
39
|
+
disabled: {
|
|
40
|
+
opacity: DISABLED_OPACITY,
|
|
41
|
+
},
|
|
42
|
+
checkContainer: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
flexDirection: 'row',
|
|
46
|
+
paddingVertical: 2,
|
|
47
|
+
gap: 12,
|
|
48
|
+
},
|
|
49
|
+
box: {
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
},
|
|
53
|
+
checkmark: {
|
|
54
|
+
position: 'absolute',
|
|
55
|
+
justifyContent: 'center',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
export default React.memo(ZSCheckBox);
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSCheckBox/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useCallback } from 'react';\nimport { Pressable, StyleSheet, ViewProps } from 'react-native';\nimport ViewAtom from '../atoms/ViewAtom';\nimport ZSText, { ZSTextProps } from '../ZSText';\nimport { useTheme } from '../../context/ThemeContext';\nimport { DISABLED_OPACITY, RADIUS } from '../../theme/tokens';\nimport { SvgCheck } from '../../assets/SvgCheck';\n\n// size 기본값(18) 기준 곡률·보더 비율 유지용\nconst BASE_SIZE = 18;\nconst BASE_BORDER_WIDTH = 1.6;\n\nexport interface ZSCheckBoxProps extends ViewProps {\n value: boolean;\n onChange: (value: boolean) => void;\n size?: number;\n activeColor?: string;\n label?: string;\n labelStyle?: ZSTextProps;\n labelComponent?: React.ReactNode;\n moreComponent?: React.ReactNode;\n disabled?: boolean;\n}\n\nfunction ZSCheckBox({\n value,\n onChange,\n size = 18,\n activeColor,\n label,\n labelStyle,\n labelComponent,\n moreComponent,\n disabled = false,\n style,\n ...props\n}: ZSCheckBoxProps) {\n const { palette } = useTheme();\n const checkedColor = activeColor ?? palette.primary.main;\n\n const handlePress = useCallback(() => {\n if (!disabled) {\n onChange(!value);\n }\n }, [disabled, onChange, value]);\n\n return (\n <ViewAtom style={[styles.container, disabled && styles.disabled, style]} {...props}>\n <Pressable\n onPress={handlePress}\n disabled={disabled}\n style={styles.checkContainer}\n accessibilityRole='checkbox'\n accessibilityState={{ checked: value, disabled }}\n >\n <ViewAtom\n style={[\n styles.box,\n {\n width: size,\n height: size,\n borderRadius: RADIUS.xs * (size / BASE_SIZE),\n borderWidth: BASE_BORDER_WIDTH * (size / BASE_SIZE),\n backgroundColor: value ? checkedColor : 'transparent',\n borderColor: value ? checkedColor : palette.grey[30],\n },\n ]}\n >\n {value && (\n <ViewAtom style={styles.checkmark}>\n <SvgCheck size={size - 4} strokeWidth=\"3.5\" />\n </ViewAtom>\n )}\n </ViewAtom>\n {labelComponent ?? (label !== undefined && <ZSText {...labelStyle}>{label}</ZSText>)}\n </Pressable>\n {moreComponent}\n </ViewAtom>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n width: '100%',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n disabled: {\n opacity: DISABLED_OPACITY,\n },\n checkContainer: {\n flex: 1,\n alignItems: 'center',\n flexDirection: 'row',\n paddingVertical: 2,\n gap: 12,\n },\n box: {\n justifyContent: 'center',\n alignItems: 'center',\n },\n checkmark: {\n position: 'absolute',\n justifyContent: 'center',\n alignItems: 'center',\n },\n});\n\nexport default React.memo(ZSCheckBox);\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
3
|
+
import type { IntentOptions, TypoSubStyle } from "../../theme/types";
|
|
4
|
+
export type ZSChipVariant = 'pastel' | 'solid' | 'stroke';
|
|
5
|
+
export interface ZSChipProps extends ViewProps {
|
|
6
|
+
/** 칩 라벨 */
|
|
7
|
+
label: string;
|
|
8
|
+
/** 선택 상태를 외부에서 제어할 때 사용 (미지정 시 내부 상태로 토글) */
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
/** 비제어 모드의 초기 선택 상태 */
|
|
11
|
+
initialSelected?: boolean;
|
|
12
|
+
/** 토글 시 다음 선택 상태를 전달 */
|
|
13
|
+
onChange?: (selected: boolean) => void;
|
|
14
|
+
intent?: IntentOptions;
|
|
15
|
+
/** 선택 상태 스타일 (기본: pastel) */
|
|
16
|
+
variant?: ZSChipVariant;
|
|
17
|
+
textSize?: TypoSubStyle;
|
|
18
|
+
/** true면 선택 시 체크 아이콘 표시 */
|
|
19
|
+
checkIcon?: boolean;
|
|
20
|
+
/** 라벨 왼쪽 커스텀 요소 */
|
|
21
|
+
leftIcon?: React.ReactNode;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
style?: StyleProp<ViewStyle>;
|
|
24
|
+
}
|
|
25
|
+
declare function ZSChip({ label, selected, initialSelected, onChange, intent, variant, textSize, checkIcon, leftIcon, disabled, style, ...props }: ZSChipProps): React.JSX.Element;
|
|
26
|
+
declare const _default: React.MemoExoticComponent<typeof ZSChip>;
|
|
27
|
+
export default _default;
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSChip/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AACnD,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAMrG,OAAO,KAAK,EAAE,aAAa,EAAoB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAQvF,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAO1D,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wBAAwB;IACxB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,2BAA2B;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,MAAM,CAAC,EACd,KAAK,EACL,QAAQ,EACR,eAAuB,EACvB,QAAQ,EACR,MAAkB,EAClB,OAAkB,EAClB,QAAc,EACd,SAAiB,EACjB,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,WAAW,qBAiFb;;AAED,wBAAkC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
|
+
import { Pressable, StyleSheet } from "react-native";
|
|
4
|
+
import Animated, { Easing, interpolateColor, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
|
|
5
|
+
import ZSText from "../ZSText";
|
|
6
|
+
import { useTheme } from "../../context/ThemeContext";
|
|
7
|
+
import { resolveTextColor } from "../../theme/resolveColor";
|
|
8
|
+
import { SvgCheck } from "../../assets/SvgCheck";
|
|
9
|
+
import { PASTEL_TEXT_COLOR, SOLID_TEXT_COLOR, STROKE_TEXT_COLOR } from "../../theme/intentColors";
|
|
10
|
+
import { DISABLED_OPACITY, DURATION, RADIUS } from "../../theme/tokens";
|
|
11
|
+
const ANIMATION_DURATION = DURATION.base;
|
|
12
|
+
const PRESS_DURATION = DURATION.press;
|
|
13
|
+
const PRESS_SCALE = 0.04;
|
|
14
|
+
// typo 크기(subStyle)별 패딩 · 체크 아이콘 크기
|
|
15
|
+
const PADDING_HORIZONTAL = { '1': 18, '2': 16, '3': 14, '4': 12, '5': 10, '6': 9 };
|
|
16
|
+
const PADDING_VERTICAL = { '1': 10, '2': 9, '3': 8, '4': 7, '5': 5, '6': 4 };
|
|
17
|
+
const CHECK_ICON_SIZE = { '1': 18, '2': 17, '3': 15, '4': 14, '5': 12, '6': 11 };
|
|
18
|
+
function ZSChip({ label, selected, initialSelected = false, onChange, intent = 'primary', variant = 'pastel', textSize = '3', checkIcon = false, leftIcon, disabled = false, style, ...props }) {
|
|
19
|
+
const { palette } = useTheme();
|
|
20
|
+
const [internalSelected, setInternalSelected] = useState(initialSelected);
|
|
21
|
+
const isSelected = selected ?? internalSelected;
|
|
22
|
+
const progress = useSharedValue(isSelected ? 1 : 0);
|
|
23
|
+
const pressProgress = useSharedValue(0);
|
|
24
|
+
const unselectedBackground = palette.background.base;
|
|
25
|
+
const unselectedBorder = palette.grey[30];
|
|
26
|
+
const selectedColors = variant === 'solid'
|
|
27
|
+
? { background: palette[intent][50], border: palette[intent][50], text: SOLID_TEXT_COLOR[intent] }
|
|
28
|
+
: variant === 'pastel'
|
|
29
|
+
? { background: palette[intent][10], border: palette[intent][30], text: PASTEL_TEXT_COLOR[intent] }
|
|
30
|
+
: { background: unselectedBackground, border: palette[intent][50], text: STROKE_TEXT_COLOR[intent] };
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
progress.value = withTiming(isSelected ? 1 : 0, {
|
|
33
|
+
duration: ANIMATION_DURATION,
|
|
34
|
+
easing: Easing.out(Easing.quad),
|
|
35
|
+
});
|
|
36
|
+
}, [isSelected, progress]);
|
|
37
|
+
const chipAnimatedStyle = useAnimatedStyle(() => ({
|
|
38
|
+
backgroundColor: interpolateColor(progress.value, [0, 1], [unselectedBackground, selectedColors.background]),
|
|
39
|
+
borderColor: interpolateColor(progress.value, [0, 1], [unselectedBorder, selectedColors.border]),
|
|
40
|
+
transform: [{ scale: 1 - pressProgress.value * PRESS_SCALE }],
|
|
41
|
+
}));
|
|
42
|
+
const handlePress = () => {
|
|
43
|
+
if (disabled)
|
|
44
|
+
return;
|
|
45
|
+
const next = !isSelected;
|
|
46
|
+
if (selected === undefined)
|
|
47
|
+
setInternalSelected(next);
|
|
48
|
+
onChange?.(next);
|
|
49
|
+
};
|
|
50
|
+
const textColor = isSelected ? selectedColors.text : 'secondary';
|
|
51
|
+
return (_jsx(Pressable, { onPress: handlePress, onPressIn: () => { pressProgress.value = withTiming(1, { duration: PRESS_DURATION }); }, onPressOut: () => { pressProgress.value = withTiming(0, { duration: PRESS_DURATION }); }, disabled: disabled, accessibilityRole: 'button', accessibilityState: { selected: isSelected, disabled }, style: styles.pressable, ...props, children: _jsxs(Animated.View, { style: [
|
|
52
|
+
styles.chip,
|
|
53
|
+
{
|
|
54
|
+
paddingHorizontal: PADDING_HORIZONTAL[textSize],
|
|
55
|
+
paddingVertical: PADDING_VERTICAL[textSize],
|
|
56
|
+
opacity: disabled ? DISABLED_OPACITY : 1,
|
|
57
|
+
},
|
|
58
|
+
chipAnimatedStyle,
|
|
59
|
+
style,
|
|
60
|
+
], children: [leftIcon, checkIcon && isSelected && (_jsx(Animated.View, { testID: 'zs-chip-check', children: _jsx(SvgCheck, { size: CHECK_ICON_SIZE[textSize], color: resolveTextColor(palette, textColor), strokeWidth: '2.4' }) })), _jsx(ZSText, { typo: `body.${textSize}`, color: textColor, allowFontScaling: false, numberOfLines: 1, children: label })] }) }));
|
|
61
|
+
}
|
|
62
|
+
export default React.memo(ZSChip);
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
// flexWrap 부모에서 콘텐츠 폭으로 렌더링되도록 hug
|
|
65
|
+
pressable: {
|
|
66
|
+
alignSelf: 'flex-start',
|
|
67
|
+
},
|
|
68
|
+
chip: {
|
|
69
|
+
flexDirection: 'row',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
gap: 5,
|
|
72
|
+
borderWidth: 1,
|
|
73
|
+
borderRadius: RADIUS.pill,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/ZSChip/index.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAkD,MAAM,cAAc,CAAC;AACrG,OAAO,QAAQ,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC3H,OAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CAAC;AACzC,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,CAAC;AAIzB,oCAAoC;AACpC,MAAM,kBAAkB,GAAiC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AACjH,MAAM,gBAAgB,GAAiC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAC3G,MAAM,eAAe,GAAiC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAuB/G,SAAS,MAAM,CAAC,EACd,KAAK,EACL,QAAQ,EACR,eAAe,GAAG,KAAK,EACvB,QAAQ,EACR,MAAM,GAAG,SAAS,EAClB,OAAO,GAAG,QAAQ,EAClB,QAAQ,GAAG,GAAG,EACd,SAAS,GAAG,KAAK,EACjB,QAAQ,EACR,QAAQ,GAAG,KAAK,EAChB,KAAK,EACL,GAAG,KAAK,EACI;IACZ,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC/B,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAG,QAAQ,IAAI,gBAAgB,CAAC;IAChD,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAExC,MAAM,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;IACrD,MAAM,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAmE,OAAO,KAAK,OAAO;QACxG,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE;QAClG,CAAC,CAAC,OAAO,KAAK,QAAQ;YACpB,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE;YACnG,CAAC,CAAC,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;IAEzG,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;YAC9C,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;SAChC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE3B,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;QAChD,eAAe,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,oBAAoB,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAC5G,WAAW,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,gBAAgB,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;QAChG,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC,KAAK,GAAG,WAAW,EAAE,CAAC;KAC9D,CAAC,CAAC,CAAC;IAEJ,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,QAAQ;YAAE,OAAO;QACrB,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC;QACzB,IAAI,QAAQ,KAAK,SAAS;YAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACtD,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,SAAS,GAAqB,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;IAEnF,OAAO,CACL,KAAC,SAAS,IACR,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,EACvF,UAAU,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,EACxF,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAC,QAAQ,EAC1B,kBAAkB,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EACtD,KAAK,EAAE,MAAM,CAAC,SAAS,KACnB,KAAK,YAET,MAAC,QAAQ,CAAC,IAAI,IACZ,KAAK,EAAE;gBACL,MAAM,CAAC,IAAI;gBACX;oBACE,iBAAiB,EAAE,kBAAkB,CAAC,QAAQ,CAAC;oBAC/C,eAAe,EAAE,gBAAgB,CAAC,QAAQ,CAAC;oBAC3C,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;iBACzC;gBACD,iBAAiB;gBACjB,KAAK;aACN,aAEA,QAAQ,EACR,SAAS,IAAI,UAAU,IAAI,CAC1B,KAAC,QAAQ,CAAC,IAAI,IAAC,MAAM,EAAC,eAAe,YACnC,KAAC,QAAQ,IACP,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,EAC/B,KAAK,EAAE,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,EAC3C,WAAW,EAAC,KAAK,GACjB,GACY,CACjB,EACD,KAAC,MAAM,IACL,IAAI,EAAE,QAAQ,QAAQ,EAAE,EACxB,KAAK,EAAE,SAAS,EAChB,gBAAgB,EAAE,KAAK,EACvB,aAAa,EAAE,CAAC,YAEf,KAAK,GACC,IACK,GACN,CACb,CAAC;AACJ,CAAC;AAED,eAAe,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAElC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,mCAAmC;IACnC,SAAS,EAAE;QACT,SAAS,EAAE,YAAY;KACxB;IACD,IAAI,EAAE;QACJ,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,QAAQ;QACpB,GAAG,EAAE,CAAC;QACN,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,MAAM,CAAC,IAAI;KAC1B;CACF,CAAC,CAAC","sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { Pressable, StyleSheet, type StyleProp, type ViewProps, type ViewStyle } from \"react-native\";\nimport Animated, { Easing, interpolateColor, useAnimatedStyle, useSharedValue, withTiming } from \"react-native-reanimated\";\nimport ZSText from \"../ZSText\";\nimport { useTheme } from \"../../context/ThemeContext\";\nimport { resolveTextColor } from \"../../theme/resolveColor\";\nimport { SvgCheck } from \"../../assets/SvgCheck\";\nimport type { IntentOptions, TypoColorOptions, TypoSubStyle } from \"../../theme/types\";\nimport { PASTEL_TEXT_COLOR, SOLID_TEXT_COLOR, STROKE_TEXT_COLOR } from \"../../theme/intentColors\";\nimport { DISABLED_OPACITY, DURATION, RADIUS } from \"../../theme/tokens\";\n\nconst ANIMATION_DURATION = DURATION.base;\nconst PRESS_DURATION = DURATION.press;\nconst PRESS_SCALE = 0.04;\n\nexport type ZSChipVariant = 'pastel' | 'solid' | 'stroke';\n\n// typo 크기(subStyle)별 패딩 · 체크 아이콘 크기\nconst PADDING_HORIZONTAL: Record<TypoSubStyle, number> = { '1': 18, '2': 16, '3': 14, '4': 12, '5': 10, '6': 9 };\nconst PADDING_VERTICAL: Record<TypoSubStyle, number> = { '1': 10, '2': 9, '3': 8, '4': 7, '5': 5, '6': 4 };\nconst CHECK_ICON_SIZE: Record<TypoSubStyle, number> = { '1': 18, '2': 17, '3': 15, '4': 14, '5': 12, '6': 11 };\n\nexport interface ZSChipProps extends ViewProps {\n /** 칩 라벨 */\n label: string;\n /** 선택 상태를 외부에서 제어할 때 사용 (미지정 시 내부 상태로 토글) */\n selected?: boolean;\n /** 비제어 모드의 초기 선택 상태 */\n initialSelected?: boolean;\n /** 토글 시 다음 선택 상태를 전달 */\n onChange?: (selected: boolean) => void;\n intent?: IntentOptions;\n /** 선택 상태 스타일 (기본: pastel) */\n variant?: ZSChipVariant;\n textSize?: TypoSubStyle;\n /** true면 선택 시 체크 아이콘 표시 */\n checkIcon?: boolean;\n /** 라벨 왼쪽 커스텀 요소 */\n leftIcon?: React.ReactNode;\n disabled?: boolean;\n style?: StyleProp<ViewStyle>;\n}\n\nfunction ZSChip({\n label,\n selected,\n initialSelected = false,\n onChange,\n intent = 'primary',\n variant = 'pastel',\n textSize = '3',\n checkIcon = false,\n leftIcon,\n disabled = false,\n style,\n ...props\n}: ZSChipProps) {\n const { palette } = useTheme();\n const [internalSelected, setInternalSelected] = useState(initialSelected);\n const isSelected = selected ?? internalSelected;\n const progress = useSharedValue(isSelected ? 1 : 0);\n const pressProgress = useSharedValue(0);\n\n const unselectedBackground = palette.background.base;\n const unselectedBorder = palette.grey[30];\n const selectedColors: { background: string; border: string; text: TypoColorOptions } = variant === 'solid'\n ? { background: palette[intent][50], border: palette[intent][50], text: SOLID_TEXT_COLOR[intent] }\n : variant === 'pastel'\n ? { background: palette[intent][10], border: palette[intent][30], text: PASTEL_TEXT_COLOR[intent] }\n : { background: unselectedBackground, border: palette[intent][50], text: STROKE_TEXT_COLOR[intent] };\n\n useEffect(() => {\n progress.value = withTiming(isSelected ? 1 : 0, {\n duration: ANIMATION_DURATION,\n easing: Easing.out(Easing.quad),\n });\n }, [isSelected, progress]);\n\n const chipAnimatedStyle = useAnimatedStyle(() => ({\n backgroundColor: interpolateColor(progress.value, [0, 1], [unselectedBackground, selectedColors.background]),\n borderColor: interpolateColor(progress.value, [0, 1], [unselectedBorder, selectedColors.border]),\n transform: [{ scale: 1 - pressProgress.value * PRESS_SCALE }],\n }));\n\n const handlePress = () => {\n if (disabled) return;\n const next = !isSelected;\n if (selected === undefined) setInternalSelected(next);\n onChange?.(next);\n };\n\n const textColor: TypoColorOptions = isSelected ? selectedColors.text : 'secondary';\n\n return (\n <Pressable\n onPress={handlePress}\n onPressIn={() => { pressProgress.value = withTiming(1, { duration: PRESS_DURATION }); }}\n onPressOut={() => { pressProgress.value = withTiming(0, { duration: PRESS_DURATION }); }}\n disabled={disabled}\n accessibilityRole='button'\n accessibilityState={{ selected: isSelected, disabled }}\n style={styles.pressable}\n {...props}\n >\n <Animated.View\n style={[\n styles.chip,\n {\n paddingHorizontal: PADDING_HORIZONTAL[textSize],\n paddingVertical: PADDING_VERTICAL[textSize],\n opacity: disabled ? DISABLED_OPACITY : 1,\n },\n chipAnimatedStyle,\n style,\n ]}\n >\n {leftIcon}\n {checkIcon && isSelected && (\n <Animated.View testID='zs-chip-check'>\n <SvgCheck\n size={CHECK_ICON_SIZE[textSize]}\n color={resolveTextColor(palette, textColor)}\n strokeWidth='2.4'\n />\n </Animated.View>\n )}\n <ZSText\n typo={`body.${textSize}`}\n color={textColor}\n allowFontScaling={false}\n numberOfLines={1}\n >\n {label}\n </ZSText>\n </Animated.View>\n </Pressable>\n );\n}\n\nexport default React.memo(ZSChip);\n\nconst styles = StyleSheet.create({\n // flexWrap 부모에서 콘텐츠 폭으로 렌더링되도록 hug\n pressable: {\n alignSelf: 'flex-start',\n },\n chip: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 5,\n borderWidth: 1,\n borderRadius: RADIUS.pill,\n },\n});\n"]}
|
|
@@ -17,6 +17,11 @@ export type ZSContainerProps = ViewProps & {
|
|
|
17
17
|
scrollToFocusedInput?: boolean;
|
|
18
18
|
foldableSingleScreen?: boolean;
|
|
19
19
|
dividerLineComponent?: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* 언폴딩 상태에서 rightComponent가 없을 때(단일 화면) 콘텐츠 최대 가로 길이(px).
|
|
22
|
+
* 전폭으로 늘리려면 false. 미지정 시 ThemeProvider의 foldable 설정값을 따른다.
|
|
23
|
+
*/
|
|
24
|
+
unfoldedSinglePaneMaxWidth?: number | false;
|
|
20
25
|
};
|
|
21
26
|
export type ZSContainerRef = ScrollView;
|
|
22
27
|
declare const ZSContainer: React.ForwardRefExoticComponent<ViewProps & {
|
|
@@ -36,6 +41,11 @@ declare const ZSContainer: React.ForwardRefExoticComponent<ViewProps & {
|
|
|
36
41
|
scrollToFocusedInput?: boolean;
|
|
37
42
|
foldableSingleScreen?: boolean;
|
|
38
43
|
dividerLineComponent?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* 언폴딩 상태에서 rightComponent가 없을 때(단일 화면) 콘텐츠 최대 가로 길이(px).
|
|
46
|
+
* 전폭으로 늘리려면 false. 미지정 시 ThemeProvider의 foldable 설정값을 따른다.
|
|
47
|
+
*/
|
|
48
|
+
unfoldedSinglePaneMaxWidth?: number | false;
|
|
39
49
|
} & React.RefAttributes<ScrollView>>;
|
|
40
50
|
export declare const styles: {
|
|
41
51
|
flex1: {
|
|
@@ -50,6 +60,15 @@ export declare const styles: {
|
|
|
50
60
|
width: "100%";
|
|
51
61
|
flexGrow: number;
|
|
52
62
|
};
|
|
63
|
+
outerLayer: {
|
|
64
|
+
flex: number;
|
|
65
|
+
width: "100%";
|
|
66
|
+
};
|
|
67
|
+
row: {
|
|
68
|
+
flex: number;
|
|
69
|
+
width: "100%";
|
|
70
|
+
flexDirection: "row";
|
|
71
|
+
};
|
|
53
72
|
};
|
|
54
73
|
export default ZSContainer;
|
|
55
74
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAsF,MAAM,OAAO,CAAC;AAC7H,OAAO,EAAE,SAAS,EAAyB,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/ZSContainer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAsF,MAAM,OAAO,CAAC;AAC7H,OAAO,EAAE,SAAS,EAAyB,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAA6B,MAAM,cAAc,CAAC;AAgBhJ,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;IACnD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACpE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,SAAS,CAAC;IACjC;;;OAGG;IACH,0BAA0B,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAExC,QAAA,MAAM,WAAW;sBAzBG,MAAM;qBACP,MAAM;eACZ,eAAe,GAAG,cAAc;YACnC,KAAK,CAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;yBAC7B,OAAO;mBACb,SAAS;sBACN,SAAS;qBACV,SAAS;mCACK,OAAO;gCACV,MAAM;kBACpB,OAAO;eACV,CAAC,KAAK,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,KAAK,IAAI;0BAC7C,MAAM;2BACL,OAAO;2BACP,OAAO;2BACP,SAAS;IAChC;;;OAGG;iCAC0B,MAAM,GAAG,KAAK;oCA0O3C,CAAC;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;CAMjB,CAAC;AAEH,eAAe,WAAW,CAAC"}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useImperativeHandle, forwardRef, useRef, useState, useCallback, useMemo } from 'react';
|
|
3
|
-
import { StatusBar, StyleSheet, ScrollView, View,
|
|
3
|
+
import { StatusBar, StyleSheet, ScrollView, View, useWindowDimensions } from 'react-native';
|
|
4
4
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
5
5
|
import { useTheme } from '../../context/ThemeContext';
|
|
6
|
+
import { transparency } from '../../theme/palette';
|
|
6
7
|
import useKeyboard from '../../model/useKeyboard';
|
|
7
8
|
import useFoldingState from '../../model/useFoldingState';
|
|
8
9
|
import { FoldingState } from '../../model/types';
|
|
9
|
-
import { MAX_FOLDABLE_SINGLE_WIDTH } from '../../model/utils';
|
|
10
|
-
const windowHeight = Dimensions.get('window').height;
|
|
11
10
|
const KEYBOARD_ANIMATION_DELAY = 50;
|
|
12
11
|
const SCROLL_VIEW_OPTIONS = {
|
|
13
12
|
bounces: false,
|
|
@@ -15,12 +14,9 @@ const SCROLL_VIEW_OPTIONS = {
|
|
|
15
14
|
keyboardShouldPersistTaps: "handled",
|
|
16
15
|
automaticallyAdjustKeyboardInsets: false
|
|
17
16
|
};
|
|
18
|
-
const ZSContainer = forwardRef(function ZSContainer({ backgroundColor, statusBarColor, barStyle, edges = ['bottom'], scrollViewDisabled = false, topComponent, bottomComponent, showsVerticalScrollIndicator = true, keyboardScrollExtraOffset = 30, translucent, scrollEventThrottle = 16, scrollToFocusedInput = true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// ---
|
|
22
|
-
...props }, forwardedRef) {
|
|
23
|
-
const { palette } = useTheme();
|
|
17
|
+
const ZSContainer = forwardRef(function ZSContainer({ backgroundColor, statusBarColor, barStyle, edges = ['bottom'], scrollViewDisabled = false, topComponent, bottomComponent, showsVerticalScrollIndicator = true, keyboardScrollExtraOffset = 30, translucent, scrollEventThrottle = 16, scrollToFocusedInput = true, foldableSingleScreen, dividerLineComponent, rightComponent, unfoldedSinglePaneMaxWidth, ...props }, forwardedRef) {
|
|
18
|
+
const { palette, foldable } = useTheme();
|
|
19
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
24
20
|
const { foldingState, width } = useFoldingState();
|
|
25
21
|
const positionRef = useRef(0);
|
|
26
22
|
const position2Ref = useRef(0);
|
|
@@ -37,12 +33,10 @@ foldableSingleScreen, dividerLineComponent, rightComponent,
|
|
|
37
33
|
const keyboardHeight = e.endCoordinates.height;
|
|
38
34
|
const safeAreaBottom = 0;
|
|
39
35
|
const availableScreenHeight = windowHeight - keyboardHeight - safeAreaBottom;
|
|
40
|
-
// 현재 스크롤 위치 참조
|
|
41
36
|
const currentScrollPosition = touchPositionRef.current === 1
|
|
42
37
|
? positionRef.current || 0
|
|
43
38
|
: position2Ref.current || 0;
|
|
44
39
|
const touchPosition = lastTouchY.current || 0;
|
|
45
|
-
// 현재 터치 위치와 스크롤 위치를 기반으로 새로운 스크롤 위치 계산
|
|
46
40
|
const scrollOffset = touchPosition - availableScreenHeight + keyboardScrollExtraOffset;
|
|
47
41
|
if (scrollTimeoutRef.current)
|
|
48
42
|
clearTimeout(scrollTimeoutRef.current);
|
|
@@ -62,7 +56,7 @@ foldableSingleScreen, dividerLineComponent, rightComponent,
|
|
|
62
56
|
scrollTimeoutRef.current = null;
|
|
63
57
|
}, KEYBOARD_ANIMATION_DELAY);
|
|
64
58
|
}
|
|
65
|
-
}, [scrollToFocusedInput, keyboardScrollExtraOffset]);
|
|
59
|
+
}, [scrollToFocusedInput, keyboardScrollExtraOffset, windowHeight]);
|
|
66
60
|
const handleKeyboardHide = useCallback(() => {
|
|
67
61
|
setKeyboardHeight(0);
|
|
68
62
|
}, []);
|
|
@@ -70,7 +64,6 @@ foldableSingleScreen, dividerLineComponent, rightComponent,
|
|
|
70
64
|
handleKeyboardShow,
|
|
71
65
|
handleKeyboardHide,
|
|
72
66
|
});
|
|
73
|
-
// 클린업
|
|
74
67
|
useEffect(() => {
|
|
75
68
|
return () => {
|
|
76
69
|
positionRef.current = null;
|
|
@@ -81,7 +74,6 @@ foldableSingleScreen, dividerLineComponent, rightComponent,
|
|
|
81
74
|
}
|
|
82
75
|
};
|
|
83
76
|
}, []);
|
|
84
|
-
// 에러 로그 출력
|
|
85
77
|
useEffect(() => {
|
|
86
78
|
if (foldableSingleScreen && (rightComponent || dividerLineComponent)) {
|
|
87
79
|
console.error('[ZSContainer] foldableSingleScreen일 때는 rightComponent/dividerLineComponent를 사용할 수 없습니다.');
|
|
@@ -115,13 +107,35 @@ foldableSingleScreen, dividerLineComponent, rightComponent,
|
|
|
115
107
|
styles.w100,
|
|
116
108
|
props.style
|
|
117
109
|
], [props.style]);
|
|
110
|
+
// prop > ThemeProvider foldable 설정 순으로 적용. 기본값(폴백) 없음 — 미주입(undefined)이거나 false면 전폭으로 채움
|
|
111
|
+
const effectiveUnfoldedMaxWidth = unfoldedSinglePaneMaxWidth ?? foldable?.unfoldedSinglePaneMaxWidth;
|
|
112
|
+
const hasRightComponent = !!rightComponent;
|
|
113
|
+
const innerMaxWidth = useMemo(() => {
|
|
114
|
+
// foldableSingleScreen 이거나, 언폴딩 + rightComponent 미지원인 단일 화면일 때 폭 제한 대상
|
|
115
|
+
const isSinglePane = foldableSingleScreen || (foldingState === FoldingState.UNFOLDED && !hasRightComponent);
|
|
116
|
+
// 주입된 폭(number)이 있을 때만 제한, 없으면 전폭으로 채움
|
|
117
|
+
if (isSinglePane && typeof effectiveUnfoldedMaxWidth === 'number') {
|
|
118
|
+
return Math.min(width, effectiveUnfoldedMaxWidth);
|
|
119
|
+
}
|
|
120
|
+
return '100%';
|
|
121
|
+
}, [foldableSingleScreen, foldingState, hasRightComponent, effectiveUnfoldedMaxWidth, width]);
|
|
122
|
+
const innerContainerStyle = useMemo(() => [
|
|
123
|
+
styles.w100,
|
|
124
|
+
{
|
|
125
|
+
maxWidth: innerMaxWidth,
|
|
126
|
+
alignSelf: 'center',
|
|
127
|
+
backgroundColor: palette.background.base,
|
|
128
|
+
},
|
|
129
|
+
], [innerMaxWidth, palette.background.base]);
|
|
118
130
|
const shouldShowStatusBar = useMemo(() => Boolean(barStyle || statusBarColor || translucent), [barStyle, statusBarColor, translucent]);
|
|
119
|
-
return (_jsxs(SafeAreaView, { style: safeAreaStyle, edges: edges, children: [_jsx(View, { style: [styles.
|
|
131
|
+
return (_jsxs(SafeAreaView, { style: safeAreaStyle, edges: edges, children: [_jsx(View, { style: [styles.outerLayer, { backgroundColor: palette.grey[100] + transparency['4%'] }], children: _jsxs(View, { style: innerContainerStyle, children: [topComponent, _jsxs(View, { style: styles.row, children: [_jsx(View, { style: styles.flex1, children: scrollViewDisabled ? (_jsx(View, { style: [styles.flex1, containerStyle], children: props.children })) : (_jsx(ScrollView, { ref: scrollViewRef, style: styles.w100, showsVerticalScrollIndicator: showsVerticalScrollIndicator, contentContainerStyle: scrollContentStyle, scrollEventThrottle: scrollEventThrottle, onScroll: (event) => { handleScroll(event, 1); }, onTouchStart: (evt) => handleTouch(evt, 1), ...SCROLL_VIEW_OPTIONS, children: _jsx(View, { style: [styles.flex1, containerStyle], children: props.children }) })) }), foldingState === FoldingState.UNFOLDED && !foldableSingleScreen && rightComponent && dividerLineComponent && dividerLineComponent, foldingState === FoldingState.UNFOLDED && !foldableSingleScreen && rightComponent && (_jsx(View, { style: styles.flex1, children: scrollViewDisabled ? (_jsx(View, { style: [styles.flex1, containerStyle], children: rightComponent })) : (_jsx(ScrollView, { ref: scrollView2Ref, style: styles.w100, showsVerticalScrollIndicator: showsVerticalScrollIndicator, contentContainerStyle: scrollContentStyle, scrollEventThrottle: scrollEventThrottle, onScroll: (event) => { handleScroll(event, 2); }, onTouchStart: (evt) => handleTouch(evt, 2), ...SCROLL_VIEW_OPTIONS, children: _jsx(View, { style: [styles.flex1, containerStyle], children: rightComponent }) })) }))] }), bottomComponent] }) }), shouldShowStatusBar && (_jsx(StatusBar, { barStyle: barStyle, backgroundColor: statusBarColor || palette.background.base, translucent: translucent }))] }));
|
|
120
132
|
});
|
|
121
133
|
export const styles = StyleSheet.create({
|
|
122
134
|
flex1: { flex: 1 },
|
|
123
135
|
w100: { flex: 1, width: '100%' },
|
|
124
136
|
scrollContainerStyle: { alignItems: 'center', width: '100%', flexGrow: 1 },
|
|
137
|
+
outerLayer: { flex: 1, width: '100%' },
|
|
138
|
+
row: { flex: 1, width: '100%', flexDirection: 'row' },
|
|
125
139
|
});
|
|
126
140
|
export default ZSContainer;
|
|
127
141
|
//# sourceMappingURL=index.js.map
|