linked-data-browser 0.0.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/.eslintrc.js +13 -0
- package/.ldo/profile.context.ts +459 -0
- package/.ldo/profile.schema.ts +751 -0
- package/.ldo/profile.shapeTypes.ts +71 -0
- package/.ldo/profile.typings.ts +295 -0
- package/.prettierignore +6 -0
- package/.prettierrc +10 -0
- package/.shapes/profile.shex +121 -0
- package/README.md +3 -0
- package/app/index.tsx +25 -0
- package/app.json +37 -0
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/babel.config.js +6 -0
- package/components/DataBrowser.tsx +57 -0
- package/components/ResourceView.tsx +14 -0
- package/components/TargetResourceProvider.tsx +128 -0
- package/components/ThemeProvider.tsx +123 -0
- package/components/nav/DialogProvider.tsx +140 -0
- package/components/nav/Layout.tsx +118 -0
- package/components/nav/header/AddressBox.tsx +126 -0
- package/components/nav/header/AvatarMenu.tsx +62 -0
- package/components/nav/header/Header.tsx +28 -0
- package/components/nav/header/SignInMenu.tsx +126 -0
- package/components/nav/header/ThemeToggleMenu.tsx +34 -0
- package/components/nav/header/ViewMenu.tsx +88 -0
- package/components/nav/useValidView.tsx +51 -0
- package/components/nav/utilityResourceViews/ErrorMessageResourceView.tsx +26 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/button.tsx +88 -0
- package/components/ui/card.tsx +101 -0
- package/components/ui/dialog.tsx +159 -0
- package/components/ui/dropdown-menu.tsx +275 -0
- package/components/ui/input.tsx +25 -0
- package/components/ui/label.tsx +34 -0
- package/components/ui/navigation-menu.tsx +200 -0
- package/components/ui/popover.tsx +45 -0
- package/components/ui/progress.tsx +79 -0
- package/components/ui/radio-group.tsx +59 -0
- package/components/ui/separator.tsx +27 -0
- package/components/ui/switch.tsx +105 -0
- package/components/ui/text.tsx +30 -0
- package/components/ui/tooltip.tsx +46 -0
- package/components.json +7 -0
- package/global.css +61 -0
- package/index.js +12 -0
- package/lib/android-navigation-bar.ts +11 -0
- package/lib/constants.ts +18 -0
- package/lib/icons/ArrowRight.tsx +4 -0
- package/lib/icons/Check.tsx +4 -0
- package/lib/icons/ChevronDown.tsx +4 -0
- package/lib/icons/ChevronRight.tsx +4 -0
- package/lib/icons/ChevronUp.tsx +4 -0
- package/lib/icons/ChevronsRight.tsx +4 -0
- package/lib/icons/CircleSlash.tsx +4 -0
- package/lib/icons/CircleX.tsx +4 -0
- package/lib/icons/Code.tsx +4 -0
- package/lib/icons/EllipsisVertical.tsx +4 -0
- package/lib/icons/EyeOff.tsx +4 -0
- package/lib/icons/File.tsx +4 -0
- package/lib/icons/Folder.tsx +4 -0
- package/lib/icons/Folders.tsx +4 -0
- package/lib/icons/Info.tsx +4 -0
- package/lib/icons/MonitorSmartphone.tsx +4 -0
- package/lib/icons/MoonStar.tsx +4 -0
- package/lib/icons/OctagonX.tsx +4 -0
- package/lib/icons/RefreshCw.tsx +4 -0
- package/lib/icons/ShieldX.tsx +4 -0
- package/lib/icons/Sun.tsx +4 -0
- package/lib/icons/TextCursorInput.tsx +4 -0
- package/lib/icons/Trash.tsx +4 -0
- package/lib/icons/ViewIcon.tsx +4 -0
- package/lib/icons/X.tsx +4 -0
- package/lib/icons/iconWithClassName.ts +14 -0
- package/lib/utils.ts +6 -0
- package/metro.config.js +6 -0
- package/nativewind-env.d.ts +1 -0
- package/package.json +89 -0
- package/resourceViews/Container/ContainerConfig.tsx +13 -0
- package/resourceViews/Container/ContainerView.tsx +148 -0
- package/resourceViews/RawCode/RawCodeConfig.tsx +11 -0
- package/resourceViews/RawCode/RawCodeEditor.tsx +37 -0
- package/resourceViews/RawCode/RawCodeView.tsx +67 -0
- package/scripts/adjust-server-paths.js +37 -0
- package/scripts/adjust-standalone-paths.js +28 -0
- package/tailwind.config.js +69 -0
- package/test-server/server-config.json +75 -0
- package/test-server/solid-css-seed.json +11 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSolidAuth } from '@ldo/solid-react';
|
|
3
|
+
import { FunctionComponent } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { AddressBox } from './AddressBox';
|
|
7
|
+
import { AvatarMenu } from './AvatarMenu';
|
|
8
|
+
import { SignInMenu } from './SignInMenu';
|
|
9
|
+
import { ViewMenu } from './ViewMenu';
|
|
10
|
+
import { Card } from '~/components/ui/card';
|
|
11
|
+
|
|
12
|
+
export const Header: FunctionComponent = () => {
|
|
13
|
+
const { session } = useSolidAuth();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Card
|
|
17
|
+
className={
|
|
18
|
+
'h-12 flex-row justify-between items-center rounded-none border-0 sm:p-2 p-1 z-[1]'
|
|
19
|
+
}
|
|
20
|
+
>
|
|
21
|
+
<AddressBox />
|
|
22
|
+
<View className="mr-1" />
|
|
23
|
+
<ViewMenu />
|
|
24
|
+
<View className="mr-1" />
|
|
25
|
+
{session.isLoggedIn ? <AvatarMenu /> : <SignInMenu />}
|
|
26
|
+
</Card>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useSolidAuth } from '@ldo/solid-react';
|
|
2
|
+
import React, { FunctionComponent, useCallback, useState } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../../ui/button';
|
|
6
|
+
import { Text } from '../../ui/text';
|
|
7
|
+
import { EllipsisVertical } from '~/lib/icons/EllipsisVertical';
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuGroup,
|
|
12
|
+
DropdownMenuItem,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuTrigger,
|
|
15
|
+
} from '~/components/ui/dropdown-menu';
|
|
16
|
+
import { ThemeToggleMenu } from './ThemeToggleMenu';
|
|
17
|
+
import {
|
|
18
|
+
Dialog,
|
|
19
|
+
DialogClose,
|
|
20
|
+
DialogContent,
|
|
21
|
+
DialogDescription,
|
|
22
|
+
DialogFooter,
|
|
23
|
+
DialogHeader,
|
|
24
|
+
DialogTitle,
|
|
25
|
+
DialogTrigger,
|
|
26
|
+
} from '~/components/ui/dialog';
|
|
27
|
+
import { Input } from '../../ui/input';
|
|
28
|
+
|
|
29
|
+
const DEFAULT_ISSUER = 'http://localhost:3000';
|
|
30
|
+
|
|
31
|
+
export const SignInMenu: FunctionComponent = () => {
|
|
32
|
+
const [idpValue, setIdpValue] = useState('');
|
|
33
|
+
const [, setIdpError] = useState<string | undefined>();
|
|
34
|
+
const { login, signUp } = useSolidAuth();
|
|
35
|
+
const onIdpSubmit = useCallback(async () => {
|
|
36
|
+
setIdpError(undefined);
|
|
37
|
+
try {
|
|
38
|
+
await login(idpValue);
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
if (err instanceof Error) {
|
|
41
|
+
setIdpError(err.message);
|
|
42
|
+
} else {
|
|
43
|
+
setIdpError('Could not log in');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, [idpValue, login]);
|
|
47
|
+
|
|
48
|
+
const renderButtonGroup = () => {
|
|
49
|
+
return (
|
|
50
|
+
<View className="flex-row">
|
|
51
|
+
<Button
|
|
52
|
+
key="signUp"
|
|
53
|
+
className="hidden sm:block"
|
|
54
|
+
onPress={() => signUp(DEFAULT_ISSUER)}
|
|
55
|
+
variant="ghost"
|
|
56
|
+
>
|
|
57
|
+
<Text>Sign Up</Text>
|
|
58
|
+
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
key="logIn"
|
|
61
|
+
className="hidden sm:block"
|
|
62
|
+
onPress={() => login(DEFAULT_ISSUER)}
|
|
63
|
+
variant="default"
|
|
64
|
+
>
|
|
65
|
+
<Text>Log In</Text>
|
|
66
|
+
</Button>
|
|
67
|
+
<DropdownMenu>
|
|
68
|
+
<DropdownMenuTrigger asChild>
|
|
69
|
+
<Button key="setMemu" variant="ghost" className="w-10">
|
|
70
|
+
<Text>
|
|
71
|
+
<EllipsisVertical size={20} />
|
|
72
|
+
</Text>
|
|
73
|
+
</Button>
|
|
74
|
+
</DropdownMenuTrigger>
|
|
75
|
+
<DropdownMenuContent className="w-64 native:w-72">
|
|
76
|
+
<DropdownMenuGroup>
|
|
77
|
+
<DropdownMenuItem
|
|
78
|
+
className="sm:hidden flex"
|
|
79
|
+
onPress={() => signUp(DEFAULT_ISSUER)}
|
|
80
|
+
>
|
|
81
|
+
<Text>Sign Up</Text>
|
|
82
|
+
</DropdownMenuItem>
|
|
83
|
+
<DropdownMenuItem
|
|
84
|
+
className="sm:hidden flex"
|
|
85
|
+
onPress={() => login(DEFAULT_ISSUER)}
|
|
86
|
+
>
|
|
87
|
+
<Text>Log In</Text>
|
|
88
|
+
</DropdownMenuItem>
|
|
89
|
+
<Dialog>
|
|
90
|
+
<DialogTrigger asChild>
|
|
91
|
+
<DropdownMenuItem closeOnPress={false}>
|
|
92
|
+
<Text>Log in with Another Pod</Text>
|
|
93
|
+
</DropdownMenuItem>
|
|
94
|
+
</DialogTrigger>
|
|
95
|
+
<DialogContent className="w-[400px]">
|
|
96
|
+
<DialogHeader>
|
|
97
|
+
<DialogTitle>Solid WebId or Identity Provider</DialogTitle>
|
|
98
|
+
<DialogDescription>
|
|
99
|
+
<Input
|
|
100
|
+
value={idpValue}
|
|
101
|
+
placeholder={DEFAULT_ISSUER}
|
|
102
|
+
onChangeText={(newText) => setIdpValue(newText)}
|
|
103
|
+
onSubmitEditing={onIdpSubmit}
|
|
104
|
+
/>
|
|
105
|
+
</DialogDescription>
|
|
106
|
+
</DialogHeader>
|
|
107
|
+
<DialogFooter>
|
|
108
|
+
<DialogClose asChild>
|
|
109
|
+
<Button onPress={onIdpSubmit}>
|
|
110
|
+
<Text>OK</Text>
|
|
111
|
+
</Button>
|
|
112
|
+
</DialogClose>
|
|
113
|
+
</DialogFooter>
|
|
114
|
+
</DialogContent>
|
|
115
|
+
</Dialog>
|
|
116
|
+
</DropdownMenuGroup>
|
|
117
|
+
<DropdownMenuSeparator />
|
|
118
|
+
<ThemeToggleMenu />
|
|
119
|
+
</DropdownMenuContent>
|
|
120
|
+
</DropdownMenu>
|
|
121
|
+
</View>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return renderButtonGroup();
|
|
126
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FunctionComponent } from 'react';
|
|
3
|
+
import { MoonStar } from '~/lib/icons/MoonStar';
|
|
4
|
+
import { Sun } from '~/lib/icons/Sun';
|
|
5
|
+
import { Text } from '../../ui/text';
|
|
6
|
+
import { Switch } from '../../ui/switch';
|
|
7
|
+
import { DropdownMenuItem } from '../../ui/dropdown-menu';
|
|
8
|
+
import { useThemeChange } from '../../ThemeProvider';
|
|
9
|
+
|
|
10
|
+
export const ThemeToggleMenu: FunctionComponent = () => {
|
|
11
|
+
const { colorScheme, setColorScheme } = useThemeChange();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<DropdownMenuItem
|
|
15
|
+
onPress={() => {
|
|
16
|
+
setColorScheme(colorScheme === 'light' ? 'dark' : 'light');
|
|
17
|
+
}}
|
|
18
|
+
className="justify-between"
|
|
19
|
+
closeOnPress={false}
|
|
20
|
+
>
|
|
21
|
+
<Text className="flex flex-row gap-1 items-center">
|
|
22
|
+
{colorScheme === 'dark' ? <MoonStar /> : <Sun />} Dark Mode
|
|
23
|
+
</Text>
|
|
24
|
+
|
|
25
|
+
<Switch
|
|
26
|
+
checked={colorScheme === 'dark'}
|
|
27
|
+
onCheckedChange={(isDark) => {
|
|
28
|
+
setColorScheme(isDark ? 'dark' : 'light');
|
|
29
|
+
}}
|
|
30
|
+
nativeID="airplane-mode"
|
|
31
|
+
/>
|
|
32
|
+
</DropdownMenuItem>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, { FunctionComponent } from 'react';
|
|
2
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
|
+
import {
|
|
4
|
+
NavigationMenu,
|
|
5
|
+
NavigationMenuContent,
|
|
6
|
+
NavigationMenuItem,
|
|
7
|
+
NavigationMenuLink,
|
|
8
|
+
NavigationMenuList,
|
|
9
|
+
NavigationMenuTrigger,
|
|
10
|
+
} from '~/components/ui/navigation-menu';
|
|
11
|
+
import { Text } from '../../ui/text';
|
|
12
|
+
import { View } from 'react-native';
|
|
13
|
+
import type { ViewRef } from '@rn-primitives/types';
|
|
14
|
+
import { cn } from '~/lib/utils';
|
|
15
|
+
import { ViewIcon } from '~/lib/icons/ViewIcon';
|
|
16
|
+
import { useValidView } from '../useValidView';
|
|
17
|
+
import { ResourceViewConfig } from '~/components/ResourceView';
|
|
18
|
+
|
|
19
|
+
export const ViewMenu: FunctionComponent = () => {
|
|
20
|
+
const insets = useSafeAreaInsets();
|
|
21
|
+
const contentInsets = {
|
|
22
|
+
top: insets.top,
|
|
23
|
+
bottom: insets.bottom,
|
|
24
|
+
left: 12,
|
|
25
|
+
right: 12,
|
|
26
|
+
};
|
|
27
|
+
const { validViews } = useValidView();
|
|
28
|
+
|
|
29
|
+
const [isOpen, setIsOpen] = React.useState<string>();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<NavigationMenu value={isOpen} onValueChange={setIsOpen}>
|
|
33
|
+
<NavigationMenuList>
|
|
34
|
+
<NavigationMenuItem value="views">
|
|
35
|
+
<NavigationMenuTrigger>
|
|
36
|
+
<Text>
|
|
37
|
+
<ViewIcon />
|
|
38
|
+
</Text>
|
|
39
|
+
<Text className="sm:block hidden">Views</Text>
|
|
40
|
+
</NavigationMenuTrigger>
|
|
41
|
+
<NavigationMenuContent insets={contentInsets}>
|
|
42
|
+
<View
|
|
43
|
+
role="list"
|
|
44
|
+
className="web:grid w-dvw gap-3 p-4 md:w-[500px] web:md:grid-cols-2 lg:w-[600px] "
|
|
45
|
+
>
|
|
46
|
+
{validViews.map((menuItem) => (
|
|
47
|
+
<ListItem key={menuItem.name} viewConfig={menuItem}>
|
|
48
|
+
{menuItem.displayName}
|
|
49
|
+
</ListItem>
|
|
50
|
+
))}
|
|
51
|
+
</View>
|
|
52
|
+
</NavigationMenuContent>
|
|
53
|
+
</NavigationMenuItem>
|
|
54
|
+
</NavigationMenuList>
|
|
55
|
+
</NavigationMenu>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ListItem = React.forwardRef<
|
|
60
|
+
ViewRef,
|
|
61
|
+
React.ComponentPropsWithoutRef<typeof View> & {
|
|
62
|
+
viewConfig: ResourceViewConfig;
|
|
63
|
+
}
|
|
64
|
+
>(({ className, viewConfig, ...props }, ref) => {
|
|
65
|
+
// TODO: add navigationn to `href` on `NavigationMenuLink` onPress
|
|
66
|
+
const { curViewConfig, setCurViewConfig } = useValidView();
|
|
67
|
+
const Icon = viewConfig.displayIcon;
|
|
68
|
+
return (
|
|
69
|
+
<View role="listitem">
|
|
70
|
+
<NavigationMenuLink
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn(
|
|
73
|
+
'web:select-none flex-row items-center overflow-hidden rounded-md p-3 leading-none no-underline text-foreground web:outline-none web:transition-colors web:hover:bg-accent active:bg-accent web:hover:text-accent-foreground web:focus:bg-accent web:focus:text-accent-foreground cursor-pointer',
|
|
74
|
+
className,
|
|
75
|
+
curViewConfig.name === viewConfig.name ? 'bg-secondary' : '',
|
|
76
|
+
)}
|
|
77
|
+
onPress={() => setCurViewConfig(viewConfig)}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
<Icon size={20} />
|
|
81
|
+
<Text className="text-sm native:text-base font-medium text-foreground leading-none ml-2">
|
|
82
|
+
{viewConfig.displayName}
|
|
83
|
+
</Text>
|
|
84
|
+
</NavigationMenuLink>
|
|
85
|
+
</View>
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
ListItem.displayName = 'ListItem';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import createContainer from 'constate';
|
|
2
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useDataBrowserConfig } from '../DataBrowser';
|
|
4
|
+
import { useTargetResource } from '../TargetResourceProvider';
|
|
5
|
+
import { ResourceViewConfig } from '../ResourceView';
|
|
6
|
+
import { EyeOff } from '~/lib/icons/EyeOff';
|
|
7
|
+
import { ErrorMessageResourceView } from './utilityResourceViews/ErrorMessageResourceView';
|
|
8
|
+
|
|
9
|
+
export const [ValidViewProvider, useValidView] = createContainer(() => {
|
|
10
|
+
const { targetUri, targetResource } = useTargetResource();
|
|
11
|
+
const { views } = useDataBrowserConfig();
|
|
12
|
+
|
|
13
|
+
const validViews = useMemo(() => {
|
|
14
|
+
const noValidView: ResourceViewConfig = {
|
|
15
|
+
name: 'noValid',
|
|
16
|
+
displayName: 'No Valid View',
|
|
17
|
+
displayIcon: EyeOff,
|
|
18
|
+
view: () => (
|
|
19
|
+
<ErrorMessageResourceView
|
|
20
|
+
icon={EyeOff}
|
|
21
|
+
message="No Views are available to display this resource."
|
|
22
|
+
/>
|
|
23
|
+
),
|
|
24
|
+
canDisplay: () => false,
|
|
25
|
+
};
|
|
26
|
+
if (
|
|
27
|
+
!targetResource ||
|
|
28
|
+
!targetUri ||
|
|
29
|
+
targetResource.type === 'InvalidIdentifierResouce'
|
|
30
|
+
) {
|
|
31
|
+
return [noValidView];
|
|
32
|
+
}
|
|
33
|
+
const potentialViews = views.filter((view) =>
|
|
34
|
+
view.canDisplay(targetUri, targetResource),
|
|
35
|
+
);
|
|
36
|
+
return potentialViews.length > 0 ? potentialViews : [noValidView];
|
|
37
|
+
}, [targetResource, targetUri, views]);
|
|
38
|
+
|
|
39
|
+
const [curViewConfig, setCurViewConfig] = useState<ResourceViewConfig>(
|
|
40
|
+
validViews[0],
|
|
41
|
+
);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setCurViewConfig(validViews[0]);
|
|
44
|
+
}, [targetUri, validViews]);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
validViews,
|
|
48
|
+
curViewConfig,
|
|
49
|
+
setCurViewConfig,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { FunctionComponent } from 'react';
|
|
2
|
+
import { Text } from '../../ui/text';
|
|
3
|
+
import { LucideIcon } from 'lucide-react-native';
|
|
4
|
+
import { Card } from '../../ui/card';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
|
|
7
|
+
interface ErrorMessageResourceViewsProps {
|
|
8
|
+
icon: LucideIcon;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ErrorMessageResourceView: FunctionComponent<
|
|
13
|
+
ErrorMessageResourceViewsProps
|
|
14
|
+
> = ({ icon, message }) => {
|
|
15
|
+
const Icon = icon;
|
|
16
|
+
return (
|
|
17
|
+
<View className="flex-1 justify-center items-center bg-background">
|
|
18
|
+
<Card className="max-w-100 p-6 items-center gap-4 m-4">
|
|
19
|
+
<Text>
|
|
20
|
+
<Icon size={60} />
|
|
21
|
+
</Text>
|
|
22
|
+
<Text>{message}</Text>
|
|
23
|
+
</Card>
|
|
24
|
+
</View>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as AvatarPrimitive from '@rn-primitives/avatar';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { cn } from '~/lib/utils';
|
|
4
|
+
|
|
5
|
+
function Avatar({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: AvatarPrimitive.RootProps & {
|
|
9
|
+
ref?: React.RefObject<AvatarPrimitive.RootRef>;
|
|
10
|
+
}) {
|
|
11
|
+
return (
|
|
12
|
+
<AvatarPrimitive.Root
|
|
13
|
+
className={cn(
|
|
14
|
+
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function AvatarImage({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: AvatarPrimitive.ImageProps & {
|
|
26
|
+
ref?: React.RefObject<AvatarPrimitive.ImageRef>;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<AvatarPrimitive.Image
|
|
30
|
+
className={cn('aspect-square h-full w-full', className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AvatarFallback({
|
|
37
|
+
className,
|
|
38
|
+
...props
|
|
39
|
+
}: AvatarPrimitive.FallbackProps & {
|
|
40
|
+
ref?: React.RefObject<AvatarPrimitive.FallbackRef>;
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
<AvatarPrimitive.Fallback
|
|
44
|
+
className={cn(
|
|
45
|
+
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Avatar, AvatarFallback, AvatarImage };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Pressable } from 'react-native';
|
|
4
|
+
import { TextClassContext } from '~/components/ui/text';
|
|
5
|
+
import { cn } from '~/lib/utils';
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
'group flex items-center justify-center rounded-md web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: 'bg-primary web:hover:opacity-90 active:opacity-90',
|
|
13
|
+
destructive: 'bg-destructive web:hover:opacity-90 active:opacity-90',
|
|
14
|
+
outline:
|
|
15
|
+
'border border-input bg-background web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent',
|
|
16
|
+
secondary: 'bg-secondary web:hover:opacity-80 active:opacity-80',
|
|
17
|
+
ghost:
|
|
18
|
+
'web:hover:bg-accent web:hover:text-accent-foreground active:bg-accent',
|
|
19
|
+
link: 'web:underline-offset-4 web:hover:underline web:focus:underline',
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default: 'h-10 px-4 py-2 native:h-12 native:px-5 native:py-3',
|
|
23
|
+
sm: 'h-9 rounded-md px-3',
|
|
24
|
+
lg: 'h-11 rounded-md px-8 native:h-14',
|
|
25
|
+
icon: 'h-10 w-10',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: 'default',
|
|
30
|
+
size: 'default',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const buttonTextVariants = cva(
|
|
36
|
+
'web:whitespace-nowrap text-sm native:text-base font-medium text-foreground web:transition-colors',
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
variant: {
|
|
40
|
+
default: 'text-primary-foreground',
|
|
41
|
+
destructive: 'text-destructive-foreground',
|
|
42
|
+
outline: 'group-active:text-accent-foreground',
|
|
43
|
+
secondary:
|
|
44
|
+
'text-secondary-foreground group-active:text-secondary-foreground',
|
|
45
|
+
ghost: 'group-active:text-accent-foreground',
|
|
46
|
+
link: 'text-primary group-active:underline',
|
|
47
|
+
},
|
|
48
|
+
size: {
|
|
49
|
+
default: '',
|
|
50
|
+
sm: '',
|
|
51
|
+
lg: 'native:text-lg',
|
|
52
|
+
icon: '',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: {
|
|
56
|
+
variant: 'default',
|
|
57
|
+
size: 'default',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
type ButtonProps = React.ComponentProps<typeof Pressable> &
|
|
63
|
+
VariantProps<typeof buttonVariants>;
|
|
64
|
+
|
|
65
|
+
function Button({ ref, className, variant, size, ...props }: ButtonProps) {
|
|
66
|
+
return (
|
|
67
|
+
<TextClassContext.Provider
|
|
68
|
+
value={buttonTextVariants({
|
|
69
|
+
variant,
|
|
70
|
+
size,
|
|
71
|
+
className: 'web:pointer-events-none',
|
|
72
|
+
})}
|
|
73
|
+
>
|
|
74
|
+
<Pressable
|
|
75
|
+
className={cn(
|
|
76
|
+
props.disabled && 'opacity-50 web:pointer-events-none',
|
|
77
|
+
buttonVariants({ variant, size, className }),
|
|
78
|
+
)}
|
|
79
|
+
ref={ref}
|
|
80
|
+
role="button"
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
</TextClassContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { Button, buttonTextVariants, buttonVariants };
|
|
88
|
+
export type { ButtonProps };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Text, TextProps, View, ViewProps } from 'react-native';
|
|
3
|
+
import { TextClassContext } from '~/components/ui/text';
|
|
4
|
+
import { cn } from '~/lib/utils';
|
|
5
|
+
|
|
6
|
+
function Card({
|
|
7
|
+
className,
|
|
8
|
+
...props
|
|
9
|
+
}: ViewProps & {
|
|
10
|
+
ref?: React.RefObject<View>;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<View
|
|
14
|
+
className={cn('rounded-lg border border-border bg-card', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function CardHeader({
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: ViewProps & {
|
|
24
|
+
ref?: React.RefObject<View>;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<View
|
|
28
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function CardTitle({
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
}: TextProps & {
|
|
38
|
+
ref?: React.RefObject<Text>;
|
|
39
|
+
}) {
|
|
40
|
+
return (
|
|
41
|
+
<Text
|
|
42
|
+
role="heading"
|
|
43
|
+
aria-level={3}
|
|
44
|
+
className={cn(
|
|
45
|
+
'text-2xl text-card-foreground font-semibold leading-none tracking-tight',
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function CardDescription({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: TextProps & {
|
|
57
|
+
ref?: React.RefObject<Text>;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<Text
|
|
61
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function CardContent({
|
|
68
|
+
className,
|
|
69
|
+
...props
|
|
70
|
+
}: ViewProps & {
|
|
71
|
+
ref?: React.RefObject<View>;
|
|
72
|
+
}) {
|
|
73
|
+
return (
|
|
74
|
+
<TextClassContext.Provider value="text-card-foreground">
|
|
75
|
+
<View className={cn('p-6 pt-0', className)} {...props} />
|
|
76
|
+
</TextClassContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function CardFooter({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: ViewProps & {
|
|
84
|
+
ref?: React.RefObject<View>;
|
|
85
|
+
}) {
|
|
86
|
+
return (
|
|
87
|
+
<View
|
|
88
|
+
className={cn('flex flex-row items-center p-6 pt-0', className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Card,
|
|
96
|
+
CardContent,
|
|
97
|
+
CardDescription,
|
|
98
|
+
CardFooter,
|
|
99
|
+
CardHeader,
|
|
100
|
+
CardTitle,
|
|
101
|
+
};
|