@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.
Files changed (41) hide show
  1. package/package.json +4 -2
  2. package/src/components/button/button.tsx +6 -3
  3. package/src/components/button/types.ts +17 -7
  4. package/src/components/card/card-section.tsx +17 -1
  5. package/src/components/card/card.tsx +2 -0
  6. package/src/components/empty-state/empty-state.tsx +42 -0
  7. package/src/components/error-state/error-state.tsx +45 -0
  8. package/src/components/form-field/form-field-context.tsx +28 -0
  9. package/src/components/form-field/form-field.tsx +74 -0
  10. package/src/components/form-scroll-view/form-scroll-view.tsx +36 -0
  11. package/src/components/icon-button/icon-button.tsx +41 -0
  12. package/src/components/input/input.tsx +61 -10
  13. package/src/components/label/label.tsx +12 -2
  14. package/src/components/list-row/list-row.tsx +78 -0
  15. package/src/components/screen-state/screen-state.tsx +35 -0
  16. package/src/components/search-bar/hooks/use-debounced-callback.ts +35 -0
  17. package/src/components/search-bar/search-bar.tsx +60 -0
  18. package/src/components/separator/separator.tsx +2 -0
  19. package/src/components/skeleton/hooks/use-reduced-motion.ts +28 -0
  20. package/src/components/skeleton/skeleton-compositions.tsx +68 -0
  21. package/src/components/skeleton/skeleton.tsx +4 -1
  22. package/src/components/skeleton/utils.ts +11 -0
  23. package/src/components/spinner/spinner.tsx +37 -0
  24. package/src/components/text/text-class-context.tsx +19 -0
  25. package/src/components/text/text.tsx +13 -4
  26. package/src/components/text/utils.ts +16 -0
  27. package/src/components/textarea/textarea.tsx +50 -0
  28. package/src/index.ts +20 -0
  29. package/src/lib/cn.ts +7 -3
  30. package/src/variants/button-variants.ts +4 -0
  31. package/src/variants/form-field-variants.ts +17 -0
  32. package/src/variants/form-scroll-view-variants.ts +3 -0
  33. package/src/variants/input-variants.ts +26 -1
  34. package/src/variants/label-variants.ts +2 -0
  35. package/src/variants/list-row-variants.ts +24 -0
  36. package/src/variants/search-bar-variants.ts +1 -0
  37. package/src/variants/skeleton-variants.ts +15 -0
  38. package/src/variants/spinner-variants.ts +15 -0
  39. package/src/variants/state-variants.ts +7 -0
  40. package/src/variants/text-variants.ts +8 -6
  41. 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 opacity = usePulseOpacity(isAnimated);
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 { cn } from "../../lib/cn";
4
- import { textVariants, type TextVariantProps } from "../../variants/text-variants";
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 className={cn(textVariants({ variant, tone }), className)} {...textProps} />
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: controlHeightClassValues }],
11
- "min-h": [{ "min-h": controlHeightClassValues }],
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>;
@@ -0,0 +1,3 @@
1
+ export const formScrollViewClassName = "flex-1";
2
+
3
+ export const formScrollViewContentClassName = "gap-md p-md";
@@ -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>;
@@ -17,4 +17,6 @@ export const labelVariants = cva("text-sm font-medium text-foreground", {
17
17
  },
18
18
  });
19
19
 
20
+ export const labelRequiredMarkerClassName = "text-destructive";
21
+
20
22
  export type LabelVariantProps = VariantProps<typeof labelVariants>;
@@ -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>;
@@ -0,0 +1,7 @@
1
+ export const stateLayoutClassName = "w-full items-center gap-sm px-md py-3xl";
2
+
3
+ export const stateIconClassName = "items-center justify-center pb-sm";
4
+
5
+ export const stateTextClassName = "text-center";
6
+
7
+ export const stateActionClassName = "pt-sm";
@@ -1,21 +1,23 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
 
3
- export const textVariants = cva("text-foreground", {
3
+ export const textVariants = cva("", {
4
4
  variants: {
5
5
  variant: {
6
6
  display: "text-3xl font-bold",
7
- heading: "text-2xl font-bold",
8
- title: "text-xl font-semibold",
9
- subtitle: "text-lg font-medium",
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
- caption: "text-sm font-normal",
12
- overline: "text-xs font-medium uppercase",
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: {