@atlure/ui 0.2.0 → 0.4.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/package.json +4 -2
- package/src/components/button/button.tsx +6 -3
- package/src/components/button/types.ts +17 -7
- package/src/components/card/card-section.tsx +17 -1
- package/src/components/card/card.tsx +2 -0
- package/src/components/empty-state/empty-state.tsx +42 -0
- package/src/components/error-state/error-state.tsx +45 -0
- package/src/components/form-field/form-field-context.tsx +28 -0
- package/src/components/form-field/form-field.tsx +74 -0
- package/src/components/form-scroll-view/form-scroll-view.tsx +36 -0
- package/src/components/icon-button/icon-button.tsx +41 -0
- package/src/components/input/input.tsx +61 -10
- package/src/components/label/label.tsx +12 -2
- package/src/components/list-row/list-row.tsx +78 -0
- package/src/components/screen-state/screen-state.tsx +35 -0
- package/src/components/search-bar/hooks/use-debounced-callback.ts +35 -0
- package/src/components/search-bar/search-bar.tsx +60 -0
- package/src/components/separator/separator.tsx +2 -0
- package/src/components/skeleton/hooks/use-reduced-motion.ts +28 -0
- package/src/components/skeleton/skeleton-compositions.tsx +68 -0
- package/src/components/skeleton/skeleton.tsx +4 -1
- package/src/components/skeleton/utils.ts +11 -0
- package/src/components/spinner/spinner.tsx +37 -0
- package/src/components/text/text-class-context.tsx +19 -0
- package/src/components/text/text.tsx +13 -4
- package/src/components/text/utils.ts +16 -0
- package/src/components/textarea/textarea.tsx +50 -0
- package/src/index.ts +20 -0
- package/src/lib/cn.ts +7 -3
- package/src/variants/button-variants.ts +4 -0
- package/src/variants/form-field-variants.ts +17 -0
- package/src/variants/form-scroll-view-variants.ts +3 -0
- package/src/variants/input-variants.ts +26 -1
- package/src/variants/label-variants.ts +2 -0
- package/src/variants/list-row-variants.ts +24 -0
- package/src/variants/search-bar-variants.ts +1 -0
- package/src/variants/skeleton-variants.ts +15 -0
- package/src/variants/spinner-variants.ts +15 -0
- package/src/variants/state-variants.ts +7 -0
- package/src/variants/text-variants.ts +8 -6
- package/src/variants/textarea-variants.ts +20 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export interface DebouncedCallback<TValue> {
|
|
4
|
+
schedule: (value: TValue) => void;
|
|
5
|
+
flush: (value: TValue) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useDebouncedCallback<TValue>(
|
|
9
|
+
callback: ((value: TValue) => void) | undefined,
|
|
10
|
+
delayMs: number,
|
|
11
|
+
): DebouncedCallback<TValue> {
|
|
12
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
13
|
+
const latestCallbackRef = useRef(callback);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
latestCallbackRef.current = callback;
|
|
17
|
+
}, [callback]);
|
|
18
|
+
|
|
19
|
+
useEffect(() => () => clearTimeout(timeoutRef.current), []);
|
|
20
|
+
|
|
21
|
+
const schedule = useCallback(
|
|
22
|
+
(value: TValue) => {
|
|
23
|
+
clearTimeout(timeoutRef.current);
|
|
24
|
+
timeoutRef.current = setTimeout(() => latestCallbackRef.current?.(value), delayMs);
|
|
25
|
+
},
|
|
26
|
+
[delayMs],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const flush = useCallback((value: TValue) => {
|
|
30
|
+
clearTimeout(timeoutRef.current);
|
|
31
|
+
latestCallbackRef.current?.(value);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
return { schedule, flush };
|
|
35
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { iconSize, Search, X } from "@atlure/icons";
|
|
2
|
+
import { Pressable } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { touchTargetHitSlop } from "../../lib/touch-target";
|
|
5
|
+
import { searchBarIconClassName } from "../../variants/search-bar-variants";
|
|
6
|
+
import { Input, type InputProps } from "../input/input";
|
|
7
|
+
import { useDebouncedCallback } from "./hooks/use-debounced-callback";
|
|
8
|
+
|
|
9
|
+
export interface SearchBarProps
|
|
10
|
+
extends Omit<InputProps, "leadingIcon" | "trailingIcon" | "isMultiline" | "value"> {
|
|
11
|
+
value: string;
|
|
12
|
+
onChangeText: (value: string) => void;
|
|
13
|
+
onChangeDebounced?: (value: string) => void;
|
|
14
|
+
debounceMs?: number;
|
|
15
|
+
clearAccessibilityLabel: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function SearchBar({
|
|
19
|
+
value,
|
|
20
|
+
onChangeText,
|
|
21
|
+
onChangeDebounced,
|
|
22
|
+
debounceMs = 300,
|
|
23
|
+
clearAccessibilityLabel,
|
|
24
|
+
...inputProps
|
|
25
|
+
}: SearchBarProps) {
|
|
26
|
+
const { schedule, flush } = useDebouncedCallback(onChangeDebounced, debounceMs);
|
|
27
|
+
|
|
28
|
+
const handleChangeText = (nextValue: string) => {
|
|
29
|
+
onChangeText(nextValue);
|
|
30
|
+
schedule(nextValue);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleClear = () => {
|
|
34
|
+
onChangeText("");
|
|
35
|
+
flush("");
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Input
|
|
40
|
+
value={value}
|
|
41
|
+
onChangeText={handleChangeText}
|
|
42
|
+
returnKeyType="search"
|
|
43
|
+
clearButtonMode="never"
|
|
44
|
+
leadingIcon={<Search size={iconSize.md} className={searchBarIconClassName} />}
|
|
45
|
+
trailingIcon={
|
|
46
|
+
value.length > 0 ? (
|
|
47
|
+
<Pressable
|
|
48
|
+
accessibilityRole="button"
|
|
49
|
+
accessibilityLabel={clearAccessibilityLabel}
|
|
50
|
+
hitSlop={touchTargetHitSlop("icon")}
|
|
51
|
+
onPress={handleClear}
|
|
52
|
+
>
|
|
53
|
+
<X size={iconSize.md} className={searchBarIconClassName} />
|
|
54
|
+
</Pressable>
|
|
55
|
+
) : undefined
|
|
56
|
+
}
|
|
57
|
+
{...inputProps}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ComponentRef, Ref } from "react";
|
|
1
2
|
import { View, type ViewProps } from "react-native";
|
|
2
3
|
|
|
3
4
|
import { cn } from "../../lib/cn";
|
|
@@ -6,6 +7,7 @@ import { separatorVariants, type SeparatorVariantProps } from "../../variants/se
|
|
|
6
7
|
export interface SeparatorProps extends Omit<ViewProps, "children"> {
|
|
7
8
|
orientation?: NonNullable<SeparatorVariantProps["orientation"]>;
|
|
8
9
|
spacing?: NonNullable<SeparatorVariantProps["spacing"]>;
|
|
10
|
+
ref?: Ref<ComponentRef<typeof View>>;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function Separator({
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { AccessibilityInfo } from "react-native";
|
|
3
|
+
|
|
4
|
+
export function useReducedMotion(): boolean {
|
|
5
|
+
const [isReducedMotion, setIsReducedMotion] = useState(false);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
let isSubscribed = true;
|
|
9
|
+
|
|
10
|
+
AccessibilityInfo.isReduceMotionEnabled().then((isEnabled) => {
|
|
11
|
+
if (isSubscribed) {
|
|
12
|
+
setIsReducedMotion(isEnabled);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const subscription = AccessibilityInfo.addEventListener(
|
|
17
|
+
"reduceMotionChanged",
|
|
18
|
+
setIsReducedMotion,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return () => {
|
|
22
|
+
isSubscribed = false;
|
|
23
|
+
subscription?.remove();
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return isReducedMotion;
|
|
28
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { skeletonCompositionVariants } from "../../variants/skeleton-variants";
|
|
5
|
+
import { Skeleton } from "./skeleton";
|
|
6
|
+
|
|
7
|
+
export interface SkeletonCompositionProps extends Omit<ViewProps, "children"> {
|
|
8
|
+
accessibilityLabel: string;
|
|
9
|
+
isAnimated?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SkeletonCard({
|
|
13
|
+
accessibilityLabel,
|
|
14
|
+
isAnimated = true,
|
|
15
|
+
className,
|
|
16
|
+
...viewProps
|
|
17
|
+
}: SkeletonCompositionProps) {
|
|
18
|
+
return (
|
|
19
|
+
<View
|
|
20
|
+
accessibilityLabel={accessibilityLabel}
|
|
21
|
+
className={cn(skeletonCompositionVariants({ composition: "card" }), className)}
|
|
22
|
+
{...viewProps}
|
|
23
|
+
>
|
|
24
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="block" />
|
|
25
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="title" />
|
|
26
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="line" />
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function SkeletonListRow({
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
isAnimated = true,
|
|
34
|
+
className,
|
|
35
|
+
...viewProps
|
|
36
|
+
}: SkeletonCompositionProps) {
|
|
37
|
+
return (
|
|
38
|
+
<View
|
|
39
|
+
accessibilityLabel={accessibilityLabel}
|
|
40
|
+
className={cn(skeletonCompositionVariants({ composition: "listRow" }), className)}
|
|
41
|
+
{...viewProps}
|
|
42
|
+
>
|
|
43
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="title" />
|
|
44
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="line" />
|
|
45
|
+
</View>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function SkeletonAvatarRow({
|
|
50
|
+
accessibilityLabel,
|
|
51
|
+
isAnimated = true,
|
|
52
|
+
className,
|
|
53
|
+
...viewProps
|
|
54
|
+
}: SkeletonCompositionProps) {
|
|
55
|
+
return (
|
|
56
|
+
<View
|
|
57
|
+
accessibilityLabel={accessibilityLabel}
|
|
58
|
+
className={cn(skeletonCompositionVariants({ composition: "avatarRow" }), className)}
|
|
59
|
+
{...viewProps}
|
|
60
|
+
>
|
|
61
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="circle" />
|
|
62
|
+
<View className={skeletonCompositionVariants({ composition: "avatarRowText" })}>
|
|
63
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="title" />
|
|
64
|
+
<Skeleton accessibilityLabel={accessibilityLabel} isAnimated={isAnimated} shape="line" />
|
|
65
|
+
</View>
|
|
66
|
+
</View>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -3,6 +3,8 @@ import { Animated, type ViewProps } from "react-native";
|
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { skeletonVariants, type SkeletonVariantProps } from "../../variants/skeleton-variants";
|
|
5
5
|
import { usePulseOpacity } from "./hooks/use-pulse-opacity";
|
|
6
|
+
import { useReducedMotion } from "./hooks/use-reduced-motion";
|
|
7
|
+
import { shouldAnimateSkeleton } from "./utils";
|
|
6
8
|
|
|
7
9
|
export interface SkeletonProps extends Omit<ViewProps, "children"> {
|
|
8
10
|
shape?: NonNullable<SkeletonVariantProps["shape"]>;
|
|
@@ -18,7 +20,8 @@ export function Skeleton({
|
|
|
18
20
|
style,
|
|
19
21
|
...viewProps
|
|
20
22
|
}: SkeletonProps) {
|
|
21
|
-
const
|
|
23
|
+
const isReducedMotion = useReducedMotion();
|
|
24
|
+
const opacity = usePulseOpacity(shouldAnimateSkeleton({ isAnimated, isReducedMotion }));
|
|
22
25
|
|
|
23
26
|
return (
|
|
24
27
|
<Animated.View
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface SkeletonAnimationInput {
|
|
2
|
+
isAnimated: boolean;
|
|
3
|
+
isReducedMotion: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function shouldAnimateSkeleton({
|
|
7
|
+
isAnimated,
|
|
8
|
+
isReducedMotion,
|
|
9
|
+
}: SkeletonAnimationInput): boolean {
|
|
10
|
+
return isAnimated && !isReducedMotion;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { semantic } from "@atlure/tokens";
|
|
2
|
+
import { ActivityIndicator, useColorScheme, type ViewProps } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
import { spinnerVariants, type SpinnerVariantProps } from "../../variants/spinner-variants";
|
|
6
|
+
|
|
7
|
+
export interface SpinnerProps extends Omit<ViewProps, "children"> {
|
|
8
|
+
size?: NonNullable<SpinnerVariantProps["size"]>;
|
|
9
|
+
accessibilityLabel: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ACTIVITY_INDICATOR_SIZE = {
|
|
13
|
+
sm: "small",
|
|
14
|
+
md: "large",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export function Spinner({
|
|
18
|
+
size = "md",
|
|
19
|
+
accessibilityLabel,
|
|
20
|
+
className,
|
|
21
|
+
...viewProps
|
|
22
|
+
}: SpinnerProps) {
|
|
23
|
+
const colorScheme = useColorScheme();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ActivityIndicator
|
|
27
|
+
accessibilityRole="progressbar"
|
|
28
|
+
accessibilityLabel={accessibilityLabel}
|
|
29
|
+
accessibilityState={{ busy: true }}
|
|
30
|
+
aria-busy
|
|
31
|
+
size={ACTIVITY_INDICATOR_SIZE[size]}
|
|
32
|
+
color={semantic[colorScheme === "dark" ? "dark" : "light"].primary}
|
|
33
|
+
className={cn(spinnerVariants({ size }), className)}
|
|
34
|
+
{...viewProps}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
const TextClassContext = createContext<string | undefined>(undefined);
|
|
4
|
+
|
|
5
|
+
export function TextClassProvider({
|
|
6
|
+
className,
|
|
7
|
+
children,
|
|
8
|
+
}: {
|
|
9
|
+
className: string | undefined;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}) {
|
|
12
|
+
return <TextClassContext.Provider value={className}>{children}</TextClassContext.Provider>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useInheritedTextClassName(): string | undefined {
|
|
16
|
+
return useContext(TextClassContext);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { TextClassContext };
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
import type { ComponentRef, Ref } from "react";
|
|
1
2
|
import { Text as ReactNativeText, type TextProps as ReactNativeTextProps } from "react-native";
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import type { TextVariantProps } from "../../variants/text-variants";
|
|
5
|
+
import { useInheritedTextClassName } from "./text-class-context";
|
|
6
|
+
import { resolveTextClassName } from "./utils";
|
|
5
7
|
|
|
6
|
-
export interface TextProps extends ReactNativeTextProps, TextVariantProps {
|
|
8
|
+
export interface TextProps extends ReactNativeTextProps, TextVariantProps {
|
|
9
|
+
ref?: Ref<ComponentRef<typeof ReactNativeText>>;
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
export function Text({ variant, tone, className, ...textProps }: TextProps) {
|
|
13
|
+
const inheritedClassName = useInheritedTextClassName();
|
|
14
|
+
|
|
9
15
|
return (
|
|
10
|
-
<ReactNativeText
|
|
16
|
+
<ReactNativeText
|
|
17
|
+
className={resolveTextClassName({ variant, tone, inheritedClassName, className })}
|
|
18
|
+
{...textProps}
|
|
19
|
+
/>
|
|
11
20
|
);
|
|
12
21
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn";
|
|
2
|
+
import { textVariants, type TextVariantProps } from "../../variants/text-variants";
|
|
3
|
+
|
|
4
|
+
export interface TextClassNameInput extends TextVariantProps {
|
|
5
|
+
inheritedClassName?: string | undefined;
|
|
6
|
+
className?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveTextClassName({
|
|
10
|
+
variant,
|
|
11
|
+
tone,
|
|
12
|
+
inheritedClassName,
|
|
13
|
+
className,
|
|
14
|
+
}: TextClassNameInput): string {
|
|
15
|
+
return cn(textVariants({ variant, tone }), inheritedClassName, className);
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { formFieldClassName } from "../../variants/form-field-variants";
|
|
5
|
+
import {
|
|
6
|
+
textareaCounterClassName,
|
|
7
|
+
textareaVariants,
|
|
8
|
+
type TextareaRows,
|
|
9
|
+
} from "../../variants/textarea-variants";
|
|
10
|
+
import { Input, type InputProps } from "../input/input";
|
|
11
|
+
import { Text } from "../text/text";
|
|
12
|
+
|
|
13
|
+
export interface TextareaProps extends Omit<InputProps, "isMultiline" | "size" | "numberOfLines"> {
|
|
14
|
+
rows?: TextareaRows;
|
|
15
|
+
hasCounter?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Textarea({
|
|
19
|
+
rows = 3,
|
|
20
|
+
hasCounter = false,
|
|
21
|
+
maxLength,
|
|
22
|
+
value,
|
|
23
|
+
className,
|
|
24
|
+
...inputProps
|
|
25
|
+
}: TextareaProps) {
|
|
26
|
+
const control = (
|
|
27
|
+
<Input
|
|
28
|
+
isMultiline
|
|
29
|
+
size="lg"
|
|
30
|
+
textAlignVertical="top"
|
|
31
|
+
maxLength={maxLength}
|
|
32
|
+
value={value}
|
|
33
|
+
className={cn(textareaVariants({ rows }), className)}
|
|
34
|
+
{...inputProps}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!hasCounter || maxLength === undefined) {
|
|
39
|
+
return control;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View className={formFieldClassName}>
|
|
44
|
+
{control}
|
|
45
|
+
<Text variant="caption" tone="muted" className={textareaCounterClassName}>
|
|
46
|
+
{`${value?.length ?? 0}/${maxLength}`}
|
|
47
|
+
</Text>
|
|
48
|
+
</View>
|
|
49
|
+
);
|
|
50
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,12 +7,25 @@ export * from "./components/button/types";
|
|
|
7
7
|
export * from "./components/card/card";
|
|
8
8
|
export * from "./components/card/card-section";
|
|
9
9
|
export * from "./components/checkbox/checkbox";
|
|
10
|
+
export * from "./components/empty-state/empty-state";
|
|
11
|
+
export * from "./components/error-state/error-state";
|
|
12
|
+
export * from "./components/form-field/form-field";
|
|
13
|
+
export * from "./components/form-field/form-field-context";
|
|
14
|
+
export * from "./components/form-scroll-view/form-scroll-view";
|
|
15
|
+
export * from "./components/icon-button/icon-button";
|
|
10
16
|
export * from "./components/input/input";
|
|
11
17
|
export * from "./components/label/label";
|
|
18
|
+
export * from "./components/list-row/list-row";
|
|
19
|
+
export * from "./components/screen-state/screen-state";
|
|
20
|
+
export * from "./components/search-bar/search-bar";
|
|
12
21
|
export * from "./components/separator/separator";
|
|
13
22
|
export * from "./components/skeleton/skeleton";
|
|
23
|
+
export * from "./components/skeleton/skeleton-compositions";
|
|
24
|
+
export * from "./components/spinner/spinner";
|
|
14
25
|
export * from "./components/switch/switch";
|
|
15
26
|
export * from "./components/text/text";
|
|
27
|
+
export * from "./components/text/text-class-context";
|
|
28
|
+
export * from "./components/textarea/textarea";
|
|
16
29
|
export * from "./lib/cn";
|
|
17
30
|
export * from "./lib/touch-target";
|
|
18
31
|
export * from "./variants/avatar-variants";
|
|
@@ -20,9 +33,16 @@ export * from "./variants/badge-variants";
|
|
|
20
33
|
export * from "./variants/button-variants";
|
|
21
34
|
export * from "./variants/card-variants";
|
|
22
35
|
export * from "./variants/checkbox-variants";
|
|
36
|
+
export * from "./variants/form-field-variants";
|
|
37
|
+
export * from "./variants/form-scroll-view-variants";
|
|
23
38
|
export * from "./variants/input-variants";
|
|
24
39
|
export * from "./variants/label-variants";
|
|
40
|
+
export * from "./variants/list-row-variants";
|
|
41
|
+
export * from "./variants/search-bar-variants";
|
|
25
42
|
export * from "./variants/separator-variants";
|
|
26
43
|
export * from "./variants/skeleton-variants";
|
|
44
|
+
export * from "./variants/spinner-variants";
|
|
45
|
+
export * from "./variants/state-variants";
|
|
27
46
|
export * from "./variants/switch-variants";
|
|
28
47
|
export * from "./variants/text-variants";
|
|
48
|
+
export * from "./variants/textarea-variants";
|
package/src/lib/cn.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { controlHeight } from "@atlure/tokens";
|
|
1
|
+
import { controlHeight, textareaHeight } from "@atlure/tokens";
|
|
2
2
|
import { type ClassValue, clsx } from "clsx";
|
|
3
3
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
4
4
|
|
|
5
5
|
const controlHeightClassValues = Object.keys(controlHeight).map((scaleKey) => `control-${scaleKey}`);
|
|
6
|
+
const textareaHeightClassValues = Object.keys(textareaHeight).map(
|
|
7
|
+
(scaleKey) => `textarea-${scaleKey}`,
|
|
8
|
+
);
|
|
9
|
+
const heightClassValues = [...controlHeightClassValues, ...textareaHeightClassValues];
|
|
6
10
|
|
|
7
11
|
const twMerge = extendTailwindMerge({
|
|
8
12
|
extend: {
|
|
9
13
|
classGroups: {
|
|
10
|
-
h: [{ h:
|
|
11
|
-
"min-h": [{ "min-h":
|
|
14
|
+
h: [{ h: heightClassValues }],
|
|
15
|
+
"min-h": [{ "min-h": heightClassValues }],
|
|
12
16
|
},
|
|
13
17
|
},
|
|
14
18
|
});
|
|
@@ -10,11 +10,13 @@ export const buttonVariants = cva(
|
|
|
10
10
|
outline: "border-border/20 bg-transparent",
|
|
11
11
|
ghost: "bg-transparent",
|
|
12
12
|
destructive: "bg-destructive",
|
|
13
|
+
link: "bg-transparent",
|
|
13
14
|
},
|
|
14
15
|
size: {
|
|
15
16
|
sm: "h-control-sm px-sm",
|
|
16
17
|
md: "h-control-md px-md",
|
|
17
18
|
lg: "h-control-lg px-lg",
|
|
19
|
+
icon: "h-control-icon w-control-icon px-0",
|
|
18
20
|
},
|
|
19
21
|
isFullWidth: {
|
|
20
22
|
true: "w-full",
|
|
@@ -42,11 +44,13 @@ export const buttonLabelVariants = cva("text-center font-medium", {
|
|
|
42
44
|
outline: "text-foreground",
|
|
43
45
|
ghost: "text-foreground",
|
|
44
46
|
destructive: "text-destructive-foreground",
|
|
47
|
+
link: "text-primary underline",
|
|
45
48
|
},
|
|
46
49
|
size: {
|
|
47
50
|
sm: "text-sm",
|
|
48
51
|
md: "text-base",
|
|
49
52
|
lg: "text-base",
|
|
53
|
+
icon: "text-base",
|
|
50
54
|
},
|
|
51
55
|
},
|
|
52
56
|
defaultVariants: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const formFieldClassName = "w-full gap-xs";
|
|
4
|
+
|
|
5
|
+
export const formFieldMessageVariants = cva("", {
|
|
6
|
+
variants: {
|
|
7
|
+
isInvalid: {
|
|
8
|
+
true: "text-destructive",
|
|
9
|
+
false: "text-muted-foreground",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
isInvalid: false,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type FormFieldMessageVariantProps = VariantProps<typeof formFieldMessageVariants>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
2
|
|
|
3
3
|
export const inputVariants = cva(
|
|
4
|
-
"w-full rounded-md border bg-input-background px-md text-base text-foreground",
|
|
4
|
+
"w-full rounded-md border bg-input-background px-md text-base text-foreground placeholder:text-muted-foreground",
|
|
5
5
|
{
|
|
6
6
|
variants: {
|
|
7
7
|
size: {
|
|
@@ -21,14 +21,39 @@ export const inputVariants = cva(
|
|
|
21
21
|
true: "h-auto min-h-control-lg py-sm",
|
|
22
22
|
false: "",
|
|
23
23
|
},
|
|
24
|
+
hasLeadingIcon: {
|
|
25
|
+
true: "pl-3xl",
|
|
26
|
+
false: "",
|
|
27
|
+
},
|
|
28
|
+
hasTrailingIcon: {
|
|
29
|
+
true: "pr-3xl",
|
|
30
|
+
false: "",
|
|
31
|
+
},
|
|
24
32
|
},
|
|
25
33
|
defaultVariants: {
|
|
26
34
|
size: "md",
|
|
27
35
|
isInvalid: false,
|
|
28
36
|
isDisabled: false,
|
|
29
37
|
isMultiline: false,
|
|
38
|
+
hasLeadingIcon: false,
|
|
39
|
+
hasTrailingIcon: false,
|
|
30
40
|
},
|
|
31
41
|
},
|
|
32
42
|
);
|
|
33
43
|
|
|
44
|
+
export const inputIconSlotVariants = cva("absolute inset-y-0 justify-center", {
|
|
45
|
+
variants: {
|
|
46
|
+
slot: {
|
|
47
|
+
leading: "pointer-events-none left-md",
|
|
48
|
+
trailing: "right-md",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
defaultVariants: {
|
|
52
|
+
slot: "leading",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const inputFieldWrapperClassName = "relative w-full justify-center";
|
|
57
|
+
|
|
34
58
|
export type InputVariantProps = VariantProps<typeof inputVariants>;
|
|
59
|
+
export type InputIconSlotVariantProps = VariantProps<typeof inputIconSlotVariants>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const listRowVariants = cva(
|
|
4
|
+
"w-full min-h-control-lg flex-row items-center gap-md px-md py-sm",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
isDisabled: {
|
|
8
|
+
true: "opacity-50",
|
|
9
|
+
false: "",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
isDisabled: false,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const listRowTextClassName = "flex-1 gap-xs";
|
|
19
|
+
|
|
20
|
+
export const listRowTrailingClassName = "items-end justify-center";
|
|
21
|
+
|
|
22
|
+
export const listRowChevronClassName = "text-muted-foreground";
|
|
23
|
+
|
|
24
|
+
export type ListRowVariantProps = VariantProps<typeof listRowVariants>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const searchBarIconClassName = "text-muted-foreground";
|
|
@@ -14,4 +14,19 @@ export const skeletonVariants = cva("bg-muted", {
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
export const skeletonCompositionVariants = cva("", {
|
|
18
|
+
variants: {
|
|
19
|
+
composition: {
|
|
20
|
+
card: "w-full gap-sm rounded-lg bg-card p-md",
|
|
21
|
+
listRow: "w-full gap-sm py-sm",
|
|
22
|
+
avatarRow: "w-full flex-row items-center gap-md py-sm",
|
|
23
|
+
avatarRowText: "flex-1 gap-sm",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
composition: "card",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
17
31
|
export type SkeletonVariantProps = VariantProps<typeof skeletonVariants>;
|
|
32
|
+
export type SkeletonCompositionVariantProps = VariantProps<typeof skeletonCompositionVariants>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const spinnerVariants = cva("self-center", {
|
|
4
|
+
variants: {
|
|
5
|
+
size: {
|
|
6
|
+
sm: "h-control-sm",
|
|
7
|
+
md: "h-control-md",
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
defaultVariants: {
|
|
11
|
+
size: "md",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type SpinnerVariantProps = VariantProps<typeof spinnerVariants>;
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
2
|
|
|
3
|
-
export const textVariants = cva("
|
|
3
|
+
export const textVariants = cva("", {
|
|
4
4
|
variants: {
|
|
5
5
|
variant: {
|
|
6
6
|
display: "text-3xl font-bold",
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
h1: "text-2xl font-bold",
|
|
8
|
+
h2: "text-xl font-semibold",
|
|
9
|
+
h3: "text-lg font-semibold",
|
|
10
10
|
body: "text-base font-normal",
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
bodySm: "text-sm font-normal",
|
|
12
|
+
label: "text-sm font-medium",
|
|
13
|
+
caption: "text-xs font-normal",
|
|
13
14
|
},
|
|
14
15
|
tone: {
|
|
15
16
|
default: "text-foreground",
|
|
16
17
|
muted: "text-muted-foreground",
|
|
17
18
|
primary: "text-primary",
|
|
18
19
|
destructive: "text-destructive",
|
|
20
|
+
inverse: "text-primary-foreground",
|
|
19
21
|
},
|
|
20
22
|
},
|
|
21
23
|
defaultVariants: {
|