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.
Files changed (91) hide show
  1. package/.eslintrc.js +13 -0
  2. package/.ldo/profile.context.ts +459 -0
  3. package/.ldo/profile.schema.ts +751 -0
  4. package/.ldo/profile.shapeTypes.ts +71 -0
  5. package/.ldo/profile.typings.ts +295 -0
  6. package/.prettierignore +6 -0
  7. package/.prettierrc +10 -0
  8. package/.shapes/profile.shex +121 -0
  9. package/README.md +3 -0
  10. package/app/index.tsx +25 -0
  11. package/app.json +37 -0
  12. package/assets/images/adaptive-icon.png +0 -0
  13. package/assets/images/favicon.png +0 -0
  14. package/assets/images/icon.png +0 -0
  15. package/assets/images/splash.png +0 -0
  16. package/babel.config.js +6 -0
  17. package/components/DataBrowser.tsx +57 -0
  18. package/components/ResourceView.tsx +14 -0
  19. package/components/TargetResourceProvider.tsx +128 -0
  20. package/components/ThemeProvider.tsx +123 -0
  21. package/components/nav/DialogProvider.tsx +140 -0
  22. package/components/nav/Layout.tsx +118 -0
  23. package/components/nav/header/AddressBox.tsx +126 -0
  24. package/components/nav/header/AvatarMenu.tsx +62 -0
  25. package/components/nav/header/Header.tsx +28 -0
  26. package/components/nav/header/SignInMenu.tsx +126 -0
  27. package/components/nav/header/ThemeToggleMenu.tsx +34 -0
  28. package/components/nav/header/ViewMenu.tsx +88 -0
  29. package/components/nav/useValidView.tsx +51 -0
  30. package/components/nav/utilityResourceViews/ErrorMessageResourceView.tsx +26 -0
  31. package/components/ui/avatar.tsx +53 -0
  32. package/components/ui/button.tsx +88 -0
  33. package/components/ui/card.tsx +101 -0
  34. package/components/ui/dialog.tsx +159 -0
  35. package/components/ui/dropdown-menu.tsx +275 -0
  36. package/components/ui/input.tsx +25 -0
  37. package/components/ui/label.tsx +34 -0
  38. package/components/ui/navigation-menu.tsx +200 -0
  39. package/components/ui/popover.tsx +45 -0
  40. package/components/ui/progress.tsx +79 -0
  41. package/components/ui/radio-group.tsx +59 -0
  42. package/components/ui/separator.tsx +27 -0
  43. package/components/ui/switch.tsx +105 -0
  44. package/components/ui/text.tsx +30 -0
  45. package/components/ui/tooltip.tsx +46 -0
  46. package/components.json +7 -0
  47. package/global.css +61 -0
  48. package/index.js +12 -0
  49. package/lib/android-navigation-bar.ts +11 -0
  50. package/lib/constants.ts +18 -0
  51. package/lib/icons/ArrowRight.tsx +4 -0
  52. package/lib/icons/Check.tsx +4 -0
  53. package/lib/icons/ChevronDown.tsx +4 -0
  54. package/lib/icons/ChevronRight.tsx +4 -0
  55. package/lib/icons/ChevronUp.tsx +4 -0
  56. package/lib/icons/ChevronsRight.tsx +4 -0
  57. package/lib/icons/CircleSlash.tsx +4 -0
  58. package/lib/icons/CircleX.tsx +4 -0
  59. package/lib/icons/Code.tsx +4 -0
  60. package/lib/icons/EllipsisVertical.tsx +4 -0
  61. package/lib/icons/EyeOff.tsx +4 -0
  62. package/lib/icons/File.tsx +4 -0
  63. package/lib/icons/Folder.tsx +4 -0
  64. package/lib/icons/Folders.tsx +4 -0
  65. package/lib/icons/Info.tsx +4 -0
  66. package/lib/icons/MonitorSmartphone.tsx +4 -0
  67. package/lib/icons/MoonStar.tsx +4 -0
  68. package/lib/icons/OctagonX.tsx +4 -0
  69. package/lib/icons/RefreshCw.tsx +4 -0
  70. package/lib/icons/ShieldX.tsx +4 -0
  71. package/lib/icons/Sun.tsx +4 -0
  72. package/lib/icons/TextCursorInput.tsx +4 -0
  73. package/lib/icons/Trash.tsx +4 -0
  74. package/lib/icons/ViewIcon.tsx +4 -0
  75. package/lib/icons/X.tsx +4 -0
  76. package/lib/icons/iconWithClassName.ts +14 -0
  77. package/lib/utils.ts +6 -0
  78. package/metro.config.js +6 -0
  79. package/nativewind-env.d.ts +1 -0
  80. package/package.json +89 -0
  81. package/resourceViews/Container/ContainerConfig.tsx +13 -0
  82. package/resourceViews/Container/ContainerView.tsx +148 -0
  83. package/resourceViews/RawCode/RawCodeConfig.tsx +11 -0
  84. package/resourceViews/RawCode/RawCodeEditor.tsx +37 -0
  85. package/resourceViews/RawCode/RawCodeView.tsx +67 -0
  86. package/scripts/adjust-server-paths.js +37 -0
  87. package/scripts/adjust-standalone-paths.js +28 -0
  88. package/tailwind.config.js +69 -0
  89. package/test-server/server-config.json +75 -0
  90. package/test-server/solid-css-seed.json +11 -0
  91. 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
+ };