@atlure/ui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/package.json +78 -0
- package/src/components/avatar/avatar.tsx +43 -0
- package/src/components/avatar/utils.ts +12 -0
- package/src/components/badge/badge.tsx +31 -0
- package/src/components/button/button.tsx +47 -0
- package/src/components/button/types.ts +21 -0
- package/src/components/card/card-section.tsx +20 -0
- package/src/components/card/card.tsx +45 -0
- package/src/components/checkbox/checkbox.tsx +50 -0
- package/src/components/input/input.tsx +34 -0
- package/src/components/label/label.tsx +23 -0
- package/src/components/separator/separator.tsx +26 -0
- package/src/components/skeleton/hooks/use-pulse-opacity.ts +40 -0
- package/src/components/skeleton/skeleton.tsx +34 -0
- package/src/components/switch/switch.tsx +44 -0
- package/src/components/text/text.tsx +12 -0
- package/src/index.ts +28 -0
- package/src/lib/cn.ts +18 -0
- package/src/lib/touch-target.ts +9 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/variants/avatar-variants.ts +40 -0
- package/src/variants/badge-variants.ts +44 -0
- package/src/variants/button-variants.ts +59 -0
- package/src/variants/card-variants.ts +35 -0
- package/src/variants/checkbox-variants.ts +46 -0
- package/src/variants/index.ts +11 -0
- package/src/variants/input-variants.ts +34 -0
- package/src/variants/label-variants.ts +20 -0
- package/src/variants/separator-variants.ts +21 -0
- package/src/variants/skeleton-variants.ts +17 -0
- package/src/variants/switch-variants.ts +33 -0
- package/src/variants/text-variants.ts +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @atlure/ui
|
|
2
|
+
|
|
3
|
+
React Native components for `atlure-paw`, styled with NativeWind against `@atlure/tokens`.
|
|
4
|
+
|
|
5
|
+
## This package ships TypeScript source, not compiled output
|
|
6
|
+
|
|
7
|
+
`exports` points straight at `./src/index.ts` and `files` includes `src`. That is deliberate and it is
|
|
8
|
+
the one thing not to "fix".
|
|
9
|
+
|
|
10
|
+
NativeWind's `className` is a **Babel-time JSX transform** (`jsxImportSource: "nativewind"`). If this
|
|
11
|
+
package were precompiled — by `react-native-builder-bob`, `tsup` or plain `tsc` — the JSX would be
|
|
12
|
+
frozen against `react/jsx-runtime`, `className` would become a prop nobody reads, and every component
|
|
13
|
+
would render unstyled with no error to explain why. The consuming app's Babel pipeline has to see the
|
|
14
|
+
original JSX.
|
|
15
|
+
|
|
16
|
+
## Consumer setup
|
|
17
|
+
|
|
18
|
+
Two steps, both required.
|
|
19
|
+
|
|
20
|
+
**1. Add this package's source to your Tailwind `content` array.** Classes live in files inside
|
|
21
|
+
`node_modules`, so Tailwind will not find them otherwise and every utility used only by this package
|
|
22
|
+
will be missing from the compiled output:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
module.exports = {
|
|
26
|
+
presets: [require("nativewind/preset"), require("@atlure/tailwind-preset")],
|
|
27
|
+
content: ["./app/**/*.{ts,tsx}", "./node_modules/@atlure/ui/src/**/*.{ts,tsx}"],
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The NativeWind preset must come first.
|
|
32
|
+
|
|
33
|
+
**2. Let Metro transpile this package.** Because it ships source, `node_modules/@atlure/ui` must not
|
|
34
|
+
be excluded from the Babel transform. In a default Expo project it already is not.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Avatar, Button, Card, CardContent, Text } from "@atlure/ui";
|
|
40
|
+
|
|
41
|
+
export function SitterCard({ sitter, onOpen, openLabel }) {
|
|
42
|
+
return (
|
|
43
|
+
<Card onPress={onOpen} accessibilityLabel={openLabel}>
|
|
44
|
+
<CardContent className="flex-row items-center gap-md">
|
|
45
|
+
<Avatar name={sitter.displayName} uri={sitter.avatarUrl} />
|
|
46
|
+
<Text variant="title">{sitter.displayName}</Text>
|
|
47
|
+
</CardContent>
|
|
48
|
+
</Card>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Components: `Text`, `Button`, `Card` (`CardHeader`, `CardContent`, `CardFooter`), `Input`, `Label`,
|
|
54
|
+
`Badge`, `Avatar`, `Separator`, `Skeleton`, `Switch`, `Checkbox`.
|
|
55
|
+
|
|
56
|
+
Every user-facing string is a prop. This package contains no display copy, so i18n stays in the app.
|
|
57
|
+
|
|
58
|
+
## Variant recipes are shared, render layers are not
|
|
59
|
+
|
|
60
|
+
The `cva` recipes live in `src/variants/` as standalone files with no React Native imports, and are
|
|
61
|
+
also exported from `@atlure/ui/variants`:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { buttonVariants, buttonLabelVariants } from "@atlure/ui/variants";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`@atlure/ui-web` imports the same recipes and applies them to DOM elements, so a variant's spacing and
|
|
68
|
+
colour are defined once for both platforms and only the rendering differs. Consequently the class
|
|
69
|
+
strings are restricted to the **React Native-safe Tailwind subset**:
|
|
70
|
+
|
|
71
|
+
| Not allowed | Use instead |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `space-x-*`, `space-y-*` | `gap-*` |
|
|
74
|
+
| `divide-*` | an explicit `Separator` |
|
|
75
|
+
| `grid`, `grid-cols-*` | `flex-row` / `flex-col` |
|
|
76
|
+
| descendant or sibling selectors | a class on the child |
|
|
77
|
+
| implicit row direction | `flex-row`, always — React Native defaults to column |
|
|
78
|
+
|
|
79
|
+
Values come from the token scales (`h-control-md`, `text-base`, `bg-primary`, `border-border/20`).
|
|
80
|
+
There is no raw hex anywhere in this package; to change a colour, edit `packages/tokens/src/tokens.ts`.
|
|
81
|
+
|
|
82
|
+
## The barrel is generated
|
|
83
|
+
|
|
84
|
+
`src/index.ts` is emitted by `scripts/generate-barrel.mjs` from a directory walk of `src`, and is
|
|
85
|
+
committed. Run `pnpm build` after adding a component — hand-editing it is pointless, since the next
|
|
86
|
+
build overwrites it, and CI's `git diff --exit-code` fails if it is stale.
|
|
87
|
+
|
|
88
|
+
This exists because a hand-maintained barrel is a guaranteed merge conflict whenever two branches add
|
|
89
|
+
a component. Modules under a `hooks/` directory and any `utils.ts` are treated as internal and left
|
|
90
|
+
out of the public surface.
|
|
91
|
+
|
|
92
|
+
## Accessibility
|
|
93
|
+
|
|
94
|
+
Every interactive component sets `accessibilityRole`, `accessibilityLabel` and `accessibilityState`,
|
|
95
|
+
**and** the matching `aria-*` alias. Both are needed: `react-native-web`, which Expo uses for the web
|
|
96
|
+
build, ignores `accessibilityState` in favour of `aria-*`, so state such as "checked" would silently
|
|
97
|
+
never be announced there.
|
|
98
|
+
|
|
99
|
+
Touch targets are guaranteed to reach 44px via `hitSlop`, computed from the `controlHeight` token
|
|
100
|
+
rather than hard-coded — `touchTargetHitSlop` pads a 36px or 40px control out to the minimum without
|
|
101
|
+
changing how it looks.
|
|
102
|
+
|
|
103
|
+
## Tests
|
|
104
|
+
|
|
105
|
+
`pnpm test` runs Vitest with React Testing Library. `react-native` is aliased to `react-native-web`
|
|
106
|
+
so components render into jsdom and can be queried by role and accessible name.
|
|
107
|
+
|
|
108
|
+
`@atlure/tokens` is resolved to `../tokens/src` for typechecking and testing, via `paths` in
|
|
109
|
+
`tsconfig.json` and an alias in `vitest.config.ts`. Published consumers still get `dist` through that
|
|
110
|
+
package's `exports`; resolving to source here means this package's checks do not depend on tokens having
|
|
111
|
+
been built first, and can never read a stale `dist`.
|
|
112
|
+
|
|
113
|
+
What that proves: press handling, disabled and loading behaviour, controlled toggle state, change
|
|
114
|
+
propagation, accessible roles and names, and the barrel generator's output.
|
|
115
|
+
|
|
116
|
+
What it does **not** prove: that NativeWind resolves any of these class strings. That requires a device
|
|
117
|
+
or simulator — a green test run here says nothing about styling.
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atlure/ui",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Atlure React Native component library: NativeWind primitives built on @atlure/tokens, shipped as untranspiled TypeScript source",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "David Moreira",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/simpxlify/atlure-ui.git",
|
|
10
|
+
"directory": "packages/ui"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"main": "./src/index.ts",
|
|
15
|
+
"types": "./src/index.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.ts",
|
|
19
|
+
"default": "./src/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"./variants": {
|
|
22
|
+
"types": "./src/variants/index.ts",
|
|
23
|
+
"default": "./src/variants/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"!src/**/*.test.ts",
|
|
30
|
+
"!src/**/*.test.tsx",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"class-variance-authority": "0.7.1",
|
|
38
|
+
"clsx": "2.1.1",
|
|
39
|
+
"tailwind-merge": "2.6.0",
|
|
40
|
+
"@atlure/tokens": "0.2.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"nativewind": "^4.2.6",
|
|
44
|
+
"react": ">=18.3.0",
|
|
45
|
+
"react-native": ">=0.81.0",
|
|
46
|
+
"react-native-reanimated": ">=3.16.0",
|
|
47
|
+
"react-native-safe-area-context": ">=4.10.0",
|
|
48
|
+
"tailwindcss": "^3.4.19"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"react-native-reanimated": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"react-native-safe-area-context": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@testing-library/jest-dom": "6.6.3",
|
|
60
|
+
"@testing-library/react": "16.3.0",
|
|
61
|
+
"@types/react": "19.1.8",
|
|
62
|
+
"jsdom": "26.1.0",
|
|
63
|
+
"nativewind": "4.2.6",
|
|
64
|
+
"react": "19.1.0",
|
|
65
|
+
"react-dom": "19.1.0",
|
|
66
|
+
"react-native": "0.81.4",
|
|
67
|
+
"react-native-web": "0.21.1",
|
|
68
|
+
"tailwindcss": "3.4.19",
|
|
69
|
+
"typescript": "5.9.3",
|
|
70
|
+
"vitest": "3.2.4"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"generate:barrel": "node scripts/generate-barrel.mjs",
|
|
74
|
+
"build": "pnpm run generate:barrel",
|
|
75
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
76
|
+
"test": "vitest run"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Image, View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import {
|
|
5
|
+
avatarFallbackVariants,
|
|
6
|
+
avatarVariants,
|
|
7
|
+
type AvatarVariantProps,
|
|
8
|
+
} from "../../variants/avatar-variants";
|
|
9
|
+
import { Text } from "../text/text";
|
|
10
|
+
import { toInitials } from "./utils";
|
|
11
|
+
|
|
12
|
+
export interface AvatarProps extends ViewProps {
|
|
13
|
+
name: string;
|
|
14
|
+
uri?: string | null;
|
|
15
|
+
size?: NonNullable<AvatarVariantProps["size"]>;
|
|
16
|
+
hasRing?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Avatar({
|
|
20
|
+
name,
|
|
21
|
+
uri = null,
|
|
22
|
+
size = "md",
|
|
23
|
+
hasRing = false,
|
|
24
|
+
className,
|
|
25
|
+
...viewProps
|
|
26
|
+
}: AvatarProps) {
|
|
27
|
+
const initials = toInitials(name);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View
|
|
31
|
+
accessibilityRole="image"
|
|
32
|
+
accessibilityLabel={name}
|
|
33
|
+
className={cn(avatarVariants({ size, hasRing }), className)}
|
|
34
|
+
{...viewProps}
|
|
35
|
+
>
|
|
36
|
+
{uri ? (
|
|
37
|
+
<Image source={{ uri }} accessibilityLabel={name} className="h-full w-full" />
|
|
38
|
+
) : (
|
|
39
|
+
<Text className={avatarFallbackVariants({ size })}>{initials}</Text>
|
|
40
|
+
)}
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MAX_INITIALS = 2;
|
|
2
|
+
|
|
3
|
+
export function toInitials(name: string): string {
|
|
4
|
+
return name
|
|
5
|
+
.trim()
|
|
6
|
+
.split(/\s+/)
|
|
7
|
+
.filter((namePart) => namePart.length > 0)
|
|
8
|
+
.slice(0, MAX_INITIALS)
|
|
9
|
+
.map((namePart) => namePart[0] ?? "")
|
|
10
|
+
.join("")
|
|
11
|
+
.toUpperCase();
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import {
|
|
5
|
+
badgeLabelVariants,
|
|
6
|
+
badgeVariants,
|
|
7
|
+
type BadgeVariantProps,
|
|
8
|
+
} from "../../variants/badge-variants";
|
|
9
|
+
import { Text } from "../text/text";
|
|
10
|
+
|
|
11
|
+
export interface BadgeProps extends ViewProps {
|
|
12
|
+
label: string;
|
|
13
|
+
variant?: NonNullable<BadgeVariantProps["variant"]>;
|
|
14
|
+
size?: NonNullable<BadgeVariantProps["size"]>;
|
|
15
|
+
labelClassName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Badge({
|
|
19
|
+
label,
|
|
20
|
+
variant = "primary",
|
|
21
|
+
size = "sm",
|
|
22
|
+
className,
|
|
23
|
+
labelClassName,
|
|
24
|
+
...viewProps
|
|
25
|
+
}: BadgeProps) {
|
|
26
|
+
return (
|
|
27
|
+
<View className={cn(badgeVariants({ variant, size }), className)} {...viewProps}>
|
|
28
|
+
<Text className={cn(badgeLabelVariants({ variant, size }), labelClassName)}>{label}</Text>
|
|
29
|
+
</View>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ActivityIndicator, Pressable } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { touchTargetHitSlop } from "../../lib/touch-target";
|
|
5
|
+
import { buttonLabelVariants, buttonVariants } from "../../variants/button-variants";
|
|
6
|
+
import { Text } from "../text/text";
|
|
7
|
+
import type { ButtonProps } from "./types";
|
|
8
|
+
|
|
9
|
+
export function Button({
|
|
10
|
+
label,
|
|
11
|
+
variant = "primary",
|
|
12
|
+
size = "md",
|
|
13
|
+
isFullWidth = false,
|
|
14
|
+
isDisabled = false,
|
|
15
|
+
isLoading = false,
|
|
16
|
+
leadingIcon,
|
|
17
|
+
trailingIcon,
|
|
18
|
+
accessibilityLabel,
|
|
19
|
+
className,
|
|
20
|
+
labelClassName,
|
|
21
|
+
...pressableProps
|
|
22
|
+
}: ButtonProps) {
|
|
23
|
+
const isPressBlocked = isDisabled || isLoading;
|
|
24
|
+
const hasAdornment = Boolean(leadingIcon ?? trailingIcon) || isLoading;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Pressable
|
|
28
|
+
accessibilityRole="button"
|
|
29
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
30
|
+
accessibilityState={{ disabled: isPressBlocked, busy: isLoading }}
|
|
31
|
+
aria-disabled={isPressBlocked}
|
|
32
|
+
aria-busy={isLoading}
|
|
33
|
+
disabled={isPressBlocked}
|
|
34
|
+
hitSlop={touchTargetHitSlop(size)}
|
|
35
|
+
className={cn(
|
|
36
|
+
buttonVariants({ variant, size, isFullWidth, isDisabled: isPressBlocked }),
|
|
37
|
+
hasAdornment && "gap-sm",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
{...pressableProps}
|
|
41
|
+
>
|
|
42
|
+
{isLoading ? <ActivityIndicator size="small" /> : leadingIcon}
|
|
43
|
+
<Text className={cn(buttonLabelVariants({ variant, size }), labelClassName)}>{label}</Text>
|
|
44
|
+
{!isLoading && trailingIcon}
|
|
45
|
+
</Pressable>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { PressableProps } from "react-native";
|
|
3
|
+
|
|
4
|
+
import type { ControlSize } from "../../lib/touch-target";
|
|
5
|
+
import type { ButtonVariantProps } from "../../variants/button-variants";
|
|
6
|
+
|
|
7
|
+
export type ButtonSize = Extract<ControlSize, "sm" | "md" | "lg">;
|
|
8
|
+
|
|
9
|
+
export interface ButtonProps
|
|
10
|
+
extends Omit<PressableProps, "children" | "disabled" | "accessibilityState"> {
|
|
11
|
+
label: string;
|
|
12
|
+
variant?: NonNullable<ButtonVariantProps["variant"]>;
|
|
13
|
+
size?: ButtonSize;
|
|
14
|
+
isFullWidth?: boolean;
|
|
15
|
+
isDisabled?: boolean;
|
|
16
|
+
isLoading?: boolean;
|
|
17
|
+
leadingIcon?: ReactNode;
|
|
18
|
+
trailingIcon?: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
labelClassName?: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { cardSectionVariants } from "../../variants/card-variants";
|
|
5
|
+
|
|
6
|
+
export interface CardSectionProps extends ViewProps {}
|
|
7
|
+
|
|
8
|
+
export function CardHeader({ className, ...viewProps }: CardSectionProps) {
|
|
9
|
+
return <View className={cn(cardSectionVariants({ section: "header" }), className)} {...viewProps} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CardContent({ className, ...viewProps }: CardSectionProps) {
|
|
13
|
+
return (
|
|
14
|
+
<View className={cn(cardSectionVariants({ section: "content" }), className)} {...viewProps} />
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function CardFooter({ className, ...viewProps }: CardSectionProps) {
|
|
19
|
+
return <View className={cn(cardSectionVariants({ section: "footer" }), className)} {...viewProps} />;
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Pressable, View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { cardVariants, type CardVariantProps } from "../../variants/card-variants";
|
|
5
|
+
|
|
6
|
+
export interface CardProps extends ViewProps, Pick<CardVariantProps, "variant"> {
|
|
7
|
+
onPress?: () => void;
|
|
8
|
+
accessibilityLabel?: string;
|
|
9
|
+
isDisabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Card({
|
|
13
|
+
variant,
|
|
14
|
+
onPress,
|
|
15
|
+
accessibilityLabel,
|
|
16
|
+
isDisabled = false,
|
|
17
|
+
className,
|
|
18
|
+
children,
|
|
19
|
+
...viewProps
|
|
20
|
+
}: CardProps) {
|
|
21
|
+
const cardClassName = cn(cardVariants({ variant, isPressable: Boolean(onPress) }), className);
|
|
22
|
+
|
|
23
|
+
if (!onPress) {
|
|
24
|
+
return (
|
|
25
|
+
<View className={cardClassName} {...viewProps}>
|
|
26
|
+
{children}
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Pressable
|
|
33
|
+
accessibilityRole="button"
|
|
34
|
+
accessibilityLabel={accessibilityLabel}
|
|
35
|
+
accessibilityState={{ disabled: isDisabled }}
|
|
36
|
+
aria-disabled={isDisabled}
|
|
37
|
+
disabled={isDisabled}
|
|
38
|
+
onPress={onPress}
|
|
39
|
+
className={cardClassName}
|
|
40
|
+
{...viewProps}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</Pressable>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Pressable, type PressableProps, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { touchTargetHitSlop } from "../../lib/touch-target";
|
|
5
|
+
import {
|
|
6
|
+
checkboxBoxVariants,
|
|
7
|
+
checkboxIndicatorVariants,
|
|
8
|
+
checkboxRowVariants,
|
|
9
|
+
} from "../../variants/checkbox-variants";
|
|
10
|
+
import { Label } from "../label/label";
|
|
11
|
+
|
|
12
|
+
export interface CheckboxProps
|
|
13
|
+
extends Omit<PressableProps, "children" | "disabled" | "onPress" | "accessibilityState"> {
|
|
14
|
+
isChecked: boolean;
|
|
15
|
+
onValueChange: (isChecked: boolean) => void;
|
|
16
|
+
label?: string;
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
isInvalid?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Checkbox({
|
|
22
|
+
isChecked,
|
|
23
|
+
onValueChange,
|
|
24
|
+
label,
|
|
25
|
+
isDisabled = false,
|
|
26
|
+
isInvalid = false,
|
|
27
|
+
accessibilityLabel,
|
|
28
|
+
className,
|
|
29
|
+
...pressableProps
|
|
30
|
+
}: CheckboxProps) {
|
|
31
|
+
return (
|
|
32
|
+
<Pressable
|
|
33
|
+
accessibilityRole="checkbox"
|
|
34
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
35
|
+
accessibilityState={{ checked: isChecked, disabled: isDisabled }}
|
|
36
|
+
aria-checked={isChecked}
|
|
37
|
+
aria-disabled={isDisabled}
|
|
38
|
+
disabled={isDisabled}
|
|
39
|
+
hitSlop={touchTargetHitSlop("md")}
|
|
40
|
+
onPress={() => onValueChange(!isChecked)}
|
|
41
|
+
className={cn(checkboxRowVariants({ isDisabled }), className)}
|
|
42
|
+
{...pressableProps}
|
|
43
|
+
>
|
|
44
|
+
<View className={checkboxBoxVariants({ isChecked, isInvalid })}>
|
|
45
|
+
<View className={checkboxIndicatorVariants({ isChecked })} />
|
|
46
|
+
</View>
|
|
47
|
+
{label ? <Label isDisabled={isDisabled} isInvalid={isInvalid}>{label}</Label> : null}
|
|
48
|
+
</Pressable>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TextInput, type TextInputProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { touchTargetHitSlop } from "../../lib/touch-target";
|
|
5
|
+
import { inputVariants, type InputVariantProps } from "../../variants/input-variants";
|
|
6
|
+
|
|
7
|
+
export interface InputProps extends Omit<TextInputProps, "editable" | "multiline"> {
|
|
8
|
+
size?: NonNullable<InputVariantProps["size"]>;
|
|
9
|
+
isDisabled?: boolean;
|
|
10
|
+
isInvalid?: boolean;
|
|
11
|
+
isMultiline?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Input({
|
|
15
|
+
size = "md",
|
|
16
|
+
isDisabled = false,
|
|
17
|
+
isInvalid = false,
|
|
18
|
+
isMultiline = false,
|
|
19
|
+
className,
|
|
20
|
+
...textInputProps
|
|
21
|
+
}: InputProps) {
|
|
22
|
+
return (
|
|
23
|
+
<TextInput
|
|
24
|
+
accessibilityState={{ disabled: isDisabled }}
|
|
25
|
+
aria-disabled={isDisabled}
|
|
26
|
+
aria-invalid={isInvalid}
|
|
27
|
+
editable={!isDisabled}
|
|
28
|
+
multiline={isMultiline}
|
|
29
|
+
hitSlop={touchTargetHitSlop(size)}
|
|
30
|
+
className={cn(inputVariants({ size, isInvalid, isDisabled, isMultiline }), className)}
|
|
31
|
+
{...textInputProps}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn";
|
|
2
|
+
import { labelVariants } from "../../variants/label-variants";
|
|
3
|
+
import { Text, type TextProps } from "../text/text";
|
|
4
|
+
|
|
5
|
+
export interface LabelProps extends Omit<TextProps, "variant" | "tone"> {
|
|
6
|
+
isDisabled?: boolean;
|
|
7
|
+
isInvalid?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Label({
|
|
11
|
+
isDisabled = false,
|
|
12
|
+
isInvalid = false,
|
|
13
|
+
className,
|
|
14
|
+
...textProps
|
|
15
|
+
}: LabelProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Text
|
|
18
|
+
accessibilityRole="text"
|
|
19
|
+
className={cn(labelVariants({ isDisabled, isInvalid }), className)}
|
|
20
|
+
{...textProps}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { View, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { separatorVariants, type SeparatorVariantProps } from "../../variants/separator-variants";
|
|
5
|
+
|
|
6
|
+
export interface SeparatorProps extends Omit<ViewProps, "children"> {
|
|
7
|
+
orientation?: NonNullable<SeparatorVariantProps["orientation"]>;
|
|
8
|
+
spacing?: NonNullable<SeparatorVariantProps["spacing"]>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Separator({
|
|
12
|
+
orientation = "horizontal",
|
|
13
|
+
spacing = "none",
|
|
14
|
+
className,
|
|
15
|
+
...viewProps
|
|
16
|
+
}: SeparatorProps) {
|
|
17
|
+
return (
|
|
18
|
+
<View
|
|
19
|
+
accessibilityRole="none"
|
|
20
|
+
accessibilityElementsHidden
|
|
21
|
+
importantForAccessibility="no-hide-descendants"
|
|
22
|
+
className={cn(separatorVariants({ orientation, spacing }), className)}
|
|
23
|
+
{...viewProps}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, Easing } from "react-native";
|
|
3
|
+
|
|
4
|
+
const PULSE_DURATION_MS = 900;
|
|
5
|
+
const MIN_OPACITY = 0.4;
|
|
6
|
+
const MAX_OPACITY = 1;
|
|
7
|
+
|
|
8
|
+
export function usePulseOpacity(isEnabled: boolean): Animated.Value {
|
|
9
|
+
const opacity = useRef(new Animated.Value(MAX_OPACITY)).current;
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!isEnabled) {
|
|
13
|
+
opacity.setValue(MAX_OPACITY);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pulse = Animated.loop(
|
|
18
|
+
Animated.sequence([
|
|
19
|
+
Animated.timing(opacity, {
|
|
20
|
+
toValue: MIN_OPACITY,
|
|
21
|
+
duration: PULSE_DURATION_MS,
|
|
22
|
+
easing: Easing.inOut(Easing.ease),
|
|
23
|
+
useNativeDriver: true,
|
|
24
|
+
}),
|
|
25
|
+
Animated.timing(opacity, {
|
|
26
|
+
toValue: MAX_OPACITY,
|
|
27
|
+
duration: PULSE_DURATION_MS,
|
|
28
|
+
easing: Easing.inOut(Easing.ease),
|
|
29
|
+
useNativeDriver: true,
|
|
30
|
+
}),
|
|
31
|
+
]),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
pulse.start();
|
|
35
|
+
|
|
36
|
+
return () => pulse.stop();
|
|
37
|
+
}, [isEnabled, opacity]);
|
|
38
|
+
|
|
39
|
+
return opacity;
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Animated, type ViewProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { skeletonVariants, type SkeletonVariantProps } from "../../variants/skeleton-variants";
|
|
5
|
+
import { usePulseOpacity } from "./hooks/use-pulse-opacity";
|
|
6
|
+
|
|
7
|
+
export interface SkeletonProps extends Omit<ViewProps, "children"> {
|
|
8
|
+
shape?: NonNullable<SkeletonVariantProps["shape"]>;
|
|
9
|
+
accessibilityLabel: string;
|
|
10
|
+
isAnimated?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Skeleton({
|
|
14
|
+
shape = "line",
|
|
15
|
+
accessibilityLabel,
|
|
16
|
+
isAnimated = true,
|
|
17
|
+
className,
|
|
18
|
+
style,
|
|
19
|
+
...viewProps
|
|
20
|
+
}: SkeletonProps) {
|
|
21
|
+
const opacity = usePulseOpacity(isAnimated);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Animated.View
|
|
25
|
+
accessibilityRole="progressbar"
|
|
26
|
+
accessibilityLabel={accessibilityLabel}
|
|
27
|
+
accessibilityState={{ busy: true }}
|
|
28
|
+
aria-busy
|
|
29
|
+
className={cn(skeletonVariants({ shape }), className)}
|
|
30
|
+
style={[{ opacity }, style]}
|
|
31
|
+
{...viewProps}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Pressable, type PressableProps, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { touchTargetHitSlop } from "../../lib/touch-target";
|
|
5
|
+
import { switchThumbVariants, switchTrackVariants } from "../../variants/switch-variants";
|
|
6
|
+
import { Label } from "../label/label";
|
|
7
|
+
|
|
8
|
+
export interface SwitchProps
|
|
9
|
+
extends Omit<PressableProps, "children" | "disabled" | "onPress" | "accessibilityState"> {
|
|
10
|
+
isChecked: boolean;
|
|
11
|
+
onValueChange: (isChecked: boolean) => void;
|
|
12
|
+
label?: string;
|
|
13
|
+
isDisabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Switch({
|
|
17
|
+
isChecked,
|
|
18
|
+
onValueChange,
|
|
19
|
+
label,
|
|
20
|
+
isDisabled = false,
|
|
21
|
+
accessibilityLabel,
|
|
22
|
+
className,
|
|
23
|
+
...pressableProps
|
|
24
|
+
}: SwitchProps) {
|
|
25
|
+
return (
|
|
26
|
+
<Pressable
|
|
27
|
+
accessibilityRole="switch"
|
|
28
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
29
|
+
accessibilityState={{ checked: isChecked, disabled: isDisabled }}
|
|
30
|
+
aria-checked={isChecked}
|
|
31
|
+
aria-disabled={isDisabled}
|
|
32
|
+
disabled={isDisabled}
|
|
33
|
+
hitSlop={touchTargetHitSlop("md")}
|
|
34
|
+
onPress={() => onValueChange(!isChecked)}
|
|
35
|
+
className={cn("flex-row items-center gap-sm", className)}
|
|
36
|
+
{...pressableProps}
|
|
37
|
+
>
|
|
38
|
+
<View className={switchTrackVariants({ isChecked, isDisabled })}>
|
|
39
|
+
<View className={switchThumbVariants({ isChecked })} />
|
|
40
|
+
</View>
|
|
41
|
+
{label ? <Label isDisabled={isDisabled}>{label}</Label> : null}
|
|
42
|
+
</Pressable>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Text as ReactNativeText, type TextProps as ReactNativeTextProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { textVariants, type TextVariantProps } from "../../variants/text-variants";
|
|
5
|
+
|
|
6
|
+
export interface TextProps extends ReactNativeTextProps, TextVariantProps {}
|
|
7
|
+
|
|
8
|
+
export function Text({ variant, tone, className, ...textProps }: TextProps) {
|
|
9
|
+
return (
|
|
10
|
+
<ReactNativeText className={cn(textVariants({ variant, tone }), className)} {...textProps} />
|
|
11
|
+
);
|
|
12
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* atlure-generated-barrel-do-not-edit */
|
|
2
|
+
|
|
3
|
+
export * from "./components/avatar/avatar";
|
|
4
|
+
export * from "./components/badge/badge";
|
|
5
|
+
export * from "./components/button/button";
|
|
6
|
+
export * from "./components/button/types";
|
|
7
|
+
export * from "./components/card/card";
|
|
8
|
+
export * from "./components/card/card-section";
|
|
9
|
+
export * from "./components/checkbox/checkbox";
|
|
10
|
+
export * from "./components/input/input";
|
|
11
|
+
export * from "./components/label/label";
|
|
12
|
+
export * from "./components/separator/separator";
|
|
13
|
+
export * from "./components/skeleton/skeleton";
|
|
14
|
+
export * from "./components/switch/switch";
|
|
15
|
+
export * from "./components/text/text";
|
|
16
|
+
export * from "./lib/cn";
|
|
17
|
+
export * from "./lib/touch-target";
|
|
18
|
+
export * from "./variants/avatar-variants";
|
|
19
|
+
export * from "./variants/badge-variants";
|
|
20
|
+
export * from "./variants/button-variants";
|
|
21
|
+
export * from "./variants/card-variants";
|
|
22
|
+
export * from "./variants/checkbox-variants";
|
|
23
|
+
export * from "./variants/input-variants";
|
|
24
|
+
export * from "./variants/label-variants";
|
|
25
|
+
export * from "./variants/separator-variants";
|
|
26
|
+
export * from "./variants/skeleton-variants";
|
|
27
|
+
export * from "./variants/switch-variants";
|
|
28
|
+
export * from "./variants/text-variants";
|
package/src/lib/cn.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { controlHeight } from "@atlure/tokens";
|
|
2
|
+
import { type ClassValue, clsx } from "clsx";
|
|
3
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
4
|
+
|
|
5
|
+
const controlHeightClassValues = Object.keys(controlHeight).map((scaleKey) => `control-${scaleKey}`);
|
|
6
|
+
|
|
7
|
+
const twMerge = extendTailwindMerge({
|
|
8
|
+
extend: {
|
|
9
|
+
classGroups: {
|
|
10
|
+
h: [{ h: controlHeightClassValues }],
|
|
11
|
+
"min-h": [{ "min-h": controlHeightClassValues }],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export function cn(...classValues: ClassValue[]): string {
|
|
17
|
+
return twMerge(clsx(classValues));
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { controlHeight } from "@atlure/tokens";
|
|
2
|
+
|
|
3
|
+
export const MIN_TOUCH_TARGET_SIZE = 44;
|
|
4
|
+
|
|
5
|
+
export type ControlSize = keyof typeof controlHeight;
|
|
6
|
+
|
|
7
|
+
export function touchTargetHitSlop(size: ControlSize): number {
|
|
8
|
+
return Math.max(0, Math.ceil((MIN_TOUCH_TARGET_SIZE - controlHeight[size]) / 2));
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const avatarVariants = cva(
|
|
4
|
+
"items-center justify-center overflow-hidden rounded-full bg-muted",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: "h-8 w-8",
|
|
9
|
+
md: "h-10 w-10",
|
|
10
|
+
lg: "h-12 w-12",
|
|
11
|
+
xl: "h-16 w-16",
|
|
12
|
+
},
|
|
13
|
+
hasRing: {
|
|
14
|
+
true: "border-2 border-primary",
|
|
15
|
+
false: "",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
size: "md",
|
|
20
|
+
hasRing: false,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const avatarFallbackVariants = cva("font-semibold uppercase text-muted-foreground", {
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
sm: "text-xs",
|
|
29
|
+
md: "text-sm",
|
|
30
|
+
lg: "text-base",
|
|
31
|
+
xl: "text-xl",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
size: "md",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type AvatarVariantProps = VariantProps<typeof avatarVariants>;
|
|
40
|
+
export type AvatarFallbackVariantProps = VariantProps<typeof avatarFallbackVariants>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const badgeVariants = cva("flex-row items-center justify-center self-start rounded-full", {
|
|
4
|
+
variants: {
|
|
5
|
+
variant: {
|
|
6
|
+
primary: "bg-primary",
|
|
7
|
+
secondary: "bg-secondary",
|
|
8
|
+
outline: "border border-border/20 bg-transparent",
|
|
9
|
+
destructive: "bg-destructive",
|
|
10
|
+
muted: "bg-muted",
|
|
11
|
+
},
|
|
12
|
+
size: {
|
|
13
|
+
sm: "px-sm py-xs",
|
|
14
|
+
md: "px-md py-xs",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
variant: "primary",
|
|
19
|
+
size: "sm",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const badgeLabelVariants = cva("font-medium", {
|
|
24
|
+
variants: {
|
|
25
|
+
variant: {
|
|
26
|
+
primary: "text-primary-foreground",
|
|
27
|
+
secondary: "text-secondary-foreground",
|
|
28
|
+
outline: "text-foreground",
|
|
29
|
+
destructive: "text-destructive-foreground",
|
|
30
|
+
muted: "text-muted-foreground",
|
|
31
|
+
},
|
|
32
|
+
size: {
|
|
33
|
+
sm: "text-xs",
|
|
34
|
+
md: "text-sm",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "primary",
|
|
39
|
+
size: "sm",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export type BadgeVariantProps = VariantProps<typeof badgeVariants>;
|
|
44
|
+
export type BadgeLabelVariantProps = VariantProps<typeof badgeLabelVariants>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const buttonVariants = cva(
|
|
4
|
+
"flex-row items-center justify-center rounded-md border border-transparent",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
primary: "bg-primary",
|
|
9
|
+
secondary: "bg-secondary",
|
|
10
|
+
outline: "border-border/20 bg-transparent",
|
|
11
|
+
ghost: "bg-transparent",
|
|
12
|
+
destructive: "bg-destructive",
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
sm: "h-control-sm px-sm",
|
|
16
|
+
md: "h-control-md px-md",
|
|
17
|
+
lg: "h-control-lg px-lg",
|
|
18
|
+
},
|
|
19
|
+
isFullWidth: {
|
|
20
|
+
true: "w-full",
|
|
21
|
+
false: "self-start",
|
|
22
|
+
},
|
|
23
|
+
isDisabled: {
|
|
24
|
+
true: "opacity-50",
|
|
25
|
+
false: "",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: "primary",
|
|
30
|
+
size: "md",
|
|
31
|
+
isFullWidth: false,
|
|
32
|
+
isDisabled: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const buttonLabelVariants = cva("text-center font-medium", {
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
primary: "text-primary-foreground",
|
|
41
|
+
secondary: "text-secondary-foreground",
|
|
42
|
+
outline: "text-foreground",
|
|
43
|
+
ghost: "text-foreground",
|
|
44
|
+
destructive: "text-destructive-foreground",
|
|
45
|
+
},
|
|
46
|
+
size: {
|
|
47
|
+
sm: "text-sm",
|
|
48
|
+
md: "text-base",
|
|
49
|
+
lg: "text-base",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
defaultVariants: {
|
|
53
|
+
variant: "primary",
|
|
54
|
+
size: "md",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type ButtonVariantProps = VariantProps<typeof buttonVariants>;
|
|
59
|
+
export type ButtonLabelVariantProps = VariantProps<typeof buttonLabelVariants>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const cardVariants = cva("overflow-hidden rounded-lg bg-card", {
|
|
4
|
+
variants: {
|
|
5
|
+
variant: {
|
|
6
|
+
outlined: "border border-border/20",
|
|
7
|
+
elevated: "border border-border/10 shadow-md",
|
|
8
|
+
flat: "border border-transparent",
|
|
9
|
+
},
|
|
10
|
+
isPressable: {
|
|
11
|
+
true: "min-h-control-lg",
|
|
12
|
+
false: "",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "outlined",
|
|
17
|
+
isPressable: false,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const cardSectionVariants = cva("", {
|
|
22
|
+
variants: {
|
|
23
|
+
section: {
|
|
24
|
+
header: "gap-xs px-md pb-sm pt-md",
|
|
25
|
+
content: "gap-sm px-md py-sm",
|
|
26
|
+
footer: "flex-row items-center justify-between px-md pb-md pt-sm",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
section: "content",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type CardVariantProps = VariantProps<typeof cardVariants>;
|
|
35
|
+
export type CardSectionVariantProps = VariantProps<typeof cardSectionVariants>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const checkboxRowVariants = cva("flex-row items-center gap-sm", {
|
|
4
|
+
variants: {
|
|
5
|
+
isDisabled: {
|
|
6
|
+
true: "opacity-50",
|
|
7
|
+
false: "",
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
defaultVariants: {
|
|
11
|
+
isDisabled: false,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const checkboxBoxVariants = cva("h-6 w-6 items-center justify-center rounded-sm border-2", {
|
|
16
|
+
variants: {
|
|
17
|
+
isChecked: {
|
|
18
|
+
true: "border-primary bg-primary",
|
|
19
|
+
false: "border-border/20 bg-input-background",
|
|
20
|
+
},
|
|
21
|
+
isInvalid: {
|
|
22
|
+
true: "border-destructive",
|
|
23
|
+
false: "",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
isChecked: false,
|
|
28
|
+
isInvalid: false,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const checkboxIndicatorVariants = cva("h-3 w-3 rounded-sm", {
|
|
33
|
+
variants: {
|
|
34
|
+
isChecked: {
|
|
35
|
+
true: "bg-primary-foreground",
|
|
36
|
+
false: "bg-transparent",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
isChecked: false,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type CheckboxRowVariantProps = VariantProps<typeof checkboxRowVariants>;
|
|
45
|
+
export type CheckboxBoxVariantProps = VariantProps<typeof checkboxBoxVariants>;
|
|
46
|
+
export type CheckboxIndicatorVariantProps = VariantProps<typeof checkboxIndicatorVariants>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./avatar-variants";
|
|
2
|
+
export * from "./badge-variants";
|
|
3
|
+
export * from "./button-variants";
|
|
4
|
+
export * from "./card-variants";
|
|
5
|
+
export * from "./checkbox-variants";
|
|
6
|
+
export * from "./input-variants";
|
|
7
|
+
export * from "./label-variants";
|
|
8
|
+
export * from "./separator-variants";
|
|
9
|
+
export * from "./skeleton-variants";
|
|
10
|
+
export * from "./switch-variants";
|
|
11
|
+
export * from "./text-variants";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const inputVariants = cva(
|
|
4
|
+
"w-full rounded-md border bg-input-background px-md text-base text-foreground",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: "h-control-sm",
|
|
9
|
+
md: "h-control-md",
|
|
10
|
+
lg: "h-control-lg",
|
|
11
|
+
},
|
|
12
|
+
isInvalid: {
|
|
13
|
+
true: "border-destructive",
|
|
14
|
+
false: "border-border/20",
|
|
15
|
+
},
|
|
16
|
+
isDisabled: {
|
|
17
|
+
true: "opacity-50",
|
|
18
|
+
false: "",
|
|
19
|
+
},
|
|
20
|
+
isMultiline: {
|
|
21
|
+
true: "h-auto min-h-control-lg py-sm",
|
|
22
|
+
false: "",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
size: "md",
|
|
27
|
+
isInvalid: false,
|
|
28
|
+
isDisabled: false,
|
|
29
|
+
isMultiline: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export type InputVariantProps = VariantProps<typeof inputVariants>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const labelVariants = cva("text-sm font-medium text-foreground", {
|
|
4
|
+
variants: {
|
|
5
|
+
isDisabled: {
|
|
6
|
+
true: "opacity-50",
|
|
7
|
+
false: "",
|
|
8
|
+
},
|
|
9
|
+
isInvalid: {
|
|
10
|
+
true: "text-destructive",
|
|
11
|
+
false: "",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
isDisabled: false,
|
|
16
|
+
isInvalid: false,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type LabelVariantProps = VariantProps<typeof labelVariants>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const separatorVariants = cva("bg-border/20", {
|
|
4
|
+
variants: {
|
|
5
|
+
orientation: {
|
|
6
|
+
horizontal: "h-px w-full",
|
|
7
|
+
vertical: "w-px self-stretch",
|
|
8
|
+
},
|
|
9
|
+
spacing: {
|
|
10
|
+
none: "",
|
|
11
|
+
sm: "my-sm",
|
|
12
|
+
md: "my-md",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
orientation: "horizontal",
|
|
17
|
+
spacing: "none",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type SeparatorVariantProps = VariantProps<typeof separatorVariants>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const skeletonVariants = cva("bg-muted", {
|
|
4
|
+
variants: {
|
|
5
|
+
shape: {
|
|
6
|
+
line: "h-4 w-full rounded-sm",
|
|
7
|
+
title: "h-6 w-2/3 rounded-sm",
|
|
8
|
+
block: "h-24 w-full rounded-lg",
|
|
9
|
+
circle: "h-10 w-10 rounded-full",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
shape: "line",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type SkeletonVariantProps = VariantProps<typeof skeletonVariants>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const switchTrackVariants = cva("h-7 w-12 flex-row items-center rounded-full p-xs", {
|
|
4
|
+
variants: {
|
|
5
|
+
isChecked: {
|
|
6
|
+
true: "justify-end bg-primary",
|
|
7
|
+
false: "justify-start bg-muted",
|
|
8
|
+
},
|
|
9
|
+
isDisabled: {
|
|
10
|
+
true: "opacity-50",
|
|
11
|
+
false: "",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
isChecked: false,
|
|
16
|
+
isDisabled: false,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const switchThumbVariants = cva("h-5 w-5 rounded-full", {
|
|
21
|
+
variants: {
|
|
22
|
+
isChecked: {
|
|
23
|
+
true: "bg-primary-foreground",
|
|
24
|
+
false: "bg-background",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
isChecked: false,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export type SwitchTrackVariantProps = VariantProps<typeof switchTrackVariants>;
|
|
33
|
+
export type SwitchThumbVariantProps = VariantProps<typeof switchThumbVariants>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const textVariants = cva("text-foreground", {
|
|
4
|
+
variants: {
|
|
5
|
+
variant: {
|
|
6
|
+
display: "text-3xl font-bold",
|
|
7
|
+
heading: "text-2xl font-bold",
|
|
8
|
+
title: "text-xl font-semibold",
|
|
9
|
+
subtitle: "text-lg font-medium",
|
|
10
|
+
body: "text-base font-normal",
|
|
11
|
+
caption: "text-sm font-normal",
|
|
12
|
+
overline: "text-xs font-medium uppercase",
|
|
13
|
+
},
|
|
14
|
+
tone: {
|
|
15
|
+
default: "text-foreground",
|
|
16
|
+
muted: "text-muted-foreground",
|
|
17
|
+
primary: "text-primary",
|
|
18
|
+
destructive: "text-destructive",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "body",
|
|
23
|
+
tone: "default",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type TextVariantProps = VariantProps<typeof textVariants>;
|