@microlight/core 0.10.0 → 0.11.1

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 (29) hide show
  1. package/dist/scripts/prepareServer.js +26 -1
  2. package/dist/server/app/layout.js +2 -18
  3. package/dist/server/app/library/[[...f_path]]/ViewFolder.js +37 -66
  4. package/dist/server/app/monitoring/MonitoringDashboard.js +167 -215
  5. package/dist/server/app/tasks/[slug]/ViewTask.js +81 -173
  6. package/dist/server/app/tasks/[slug]/runs/[r_id]/ViewRun.js +68 -164
  7. package/dist/server/app/tasks/[slug]/runs/[r_id]/_components/DropdownActions/DropdownActions.js +19 -25
  8. package/dist/server/components/Link.js +6 -15
  9. package/dist/server/components/MLInput.js +19 -22
  10. package/dist/server/components/Navbar/Navbar.js +14 -51
  11. package/dist/server/components/Navbar/NavbarContainer.js +7 -20
  12. package/dist/server/components/PageHeader.js +22 -54
  13. package/dist/server/components/StatusChip.js +5 -5
  14. package/dist/server/components/ui/alert.js +61 -0
  15. package/dist/server/components/ui/badge.js +37 -0
  16. package/dist/server/components/ui/breadcrumb.js +82 -0
  17. package/dist/server/components/ui/button.js +65 -0
  18. package/dist/server/components/ui/card.js +81 -0
  19. package/dist/server/components/ui/dropdown-menu.js +222 -0
  20. package/dist/server/components/ui/input.js +21 -0
  21. package/dist/server/components/ui/label.js +20 -0
  22. package/dist/server/components/ui/select.js +165 -0
  23. package/dist/server/components/ui/stack.js +104 -0
  24. package/dist/server/components/ui/table.js +77 -0
  25. package/dist/server/components/ui/tabs.js +59 -0
  26. package/dist/server/components/ui/typography.js +229 -0
  27. package/dist/server/utils/css/cn.js +11 -0
  28. package/package.json +15 -3
  29. package/dist/server/components/Icon.js +0 -22
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
- import { Link as JoyLink } from '@mui/joy';
5
4
  import NextLink from 'next/link';
6
5
  import PropTypes from 'prop-types';
6
+ import { cn } from "../utils/css/cn";
7
7
  export default function Link({
8
8
  href = '/',
9
- sx = {},
9
+ className = '',
10
10
  target = '_self',
11
11
  onClick = () => {},
12
12
  children,
@@ -18,33 +18,24 @@ export default function Link({
18
18
  if (onClick) {
19
19
  onClick(e);
20
20
  }
21
- }} sx={sx}>
21
+ }} className={className}>
22
22
  {children}
23
23
  </Component>
24
24
  </NextLink>;
25
25
  }
26
- const defaultSx = {
27
- backgroundColor: "transparent",
28
- "&:hover": {
29
- backgroundColor: "none"
30
- }
31
- };
32
- return <JoyLink color="neutral" component={NextLink} underline="none" target={target} href={href} sx={{
33
- ...defaultSx,
34
- ...sx
35
- }} onClick={e => {
26
+ return <NextLink href={href} target={target} className={cn("text-foreground no-underline bg-transparent hover:bg-transparent", className)} onClick={e => {
36
27
  if (onClick) {
37
28
  onClick(e);
38
29
  }
39
30
  }}>
40
31
  {children}
41
- </JoyLink>;
32
+ </NextLink>;
42
33
  }
43
34
 
44
35
  // For Storybook Documentation
45
36
  Link.propTypes = {
46
37
  children: PropTypes.node.isRequired,
47
- sx: PropTypes.object,
38
+ className: PropTypes.string,
48
39
  onClick: PropTypes.func,
49
40
  Component: PropTypes.elementType,
50
41
  href: PropTypes.string.isRequired,
@@ -1,29 +1,26 @@
1
- import { FormControl, FormLabel, Input, Select, Option } from "@mui/joy";
1
+ import { Label } from "./ui/label";
2
+ import { Input } from "./ui/input";
3
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
2
4
  export default function MLInput({
3
5
  def,
4
6
  slug,
5
7
  searchParams
6
8
  }) {
7
- return <>
8
- <FormControl required={def.required} sx={{
9
- mb: 2
10
- }}>
11
- <FormLabel sx={{
12
- mb: 0.25
13
- }}>{def.name}:</FormLabel>
14
- {['string', 'number', 'date'].includes(def.type) && <Input size="sm" name={slug} placeholder={def.placeholder} defaultValue={searchParams[slug] || def.default || ""} type={def.type} />}
15
- {def.type === 'file' &&
16
- // Placeholder for future file input implementation
17
- <Input size="sm" name={slug} placeholder={def.placeholder} type="file"
18
- // Add any file-specific props here
19
- />}
20
- {def.type === 'dropdown' && <Select size="sm" name={slug} placeholder={def.placeholder} defaultValue={searchParams[slug] || def.default || ""}>
21
- {def.options?.map(option => <Option key={option.value} value={option.value}>
22
- {option.label || option.value}
23
- </Option>)}
9
+ return <div className="mb-4">
10
+ <Label className="mb-1 block">
11
+ {def.name}:{def.required && <span className="text-destructive ml-1">*</span>}
12
+ </Label>
13
+ {['string', 'number', 'date'].includes(def.type) && <Input size="sm" name={slug} placeholder={def.placeholder} defaultValue={searchParams[slug] || def.default || ""} type={def.type === 'string' ? 'text' : def.type} required={def.required} />}
14
+ {def.type === 'file' && <Input size="sm" name={slug} placeholder={def.placeholder} type="file" required={def.required} />}
15
+ {def.type === 'dropdown' && <Select name={slug} defaultValue={searchParams[slug] || def.default || ""} required={def.required}>
16
+ <SelectTrigger size="sm">
17
+ <SelectValue placeholder={def.placeholder} />
18
+ </SelectTrigger>
19
+ <SelectContent>
20
+ {def.options?.map(option => <SelectItem key={option.value} value={option.value}>
21
+ {option.label || option.value}
22
+ </SelectItem>)}
23
+ </SelectContent>
24
24
  </Select>}
25
- </FormControl>
26
- {/* string input - {slug} <br/> */}
27
- {/* <pre>{JSON.stringify(def,null,2)}</pre> */}
28
- </>;
25
+ </div>;
29
26
  }
@@ -2,68 +2,31 @@
2
2
 
3
3
  import Link from 'next/link';
4
4
  import { usePathname } from 'next/navigation';
5
- // import Logo from '../Logo';
6
- import { Box, Sheet, Button } from '@mui/joy';
5
+ import { Button } from "../ui/button";
7
6
  export default function Navbar({
8
7
  user,
9
8
  signOut
10
9
  }) {
11
10
  const pathname = usePathname();
12
- return <Sheet component="nav" sx={{
13
- px: 1,
14
- // py: 0.5,
15
- display: 'flex',
16
- alignItems: 'center',
17
- justifyContent: 'space-between',
18
- // boxShadow: 'sm',
19
- borderBottom: '1px solid',
20
- borderColor: 'divider',
21
- position: 'fixed',
22
- top: 0,
23
- left: 0,
24
- right: 0,
25
- height: '40px',
26
- zIndex: 1000,
27
- bgcolor: 'background.surface'
28
- // bgcolor: 'white',
29
- }}>
30
- <Box sx={{
31
- display: 'flex',
32
- alignItems: 'center',
33
- gap: 2
34
- }}>
35
- {/* <Logo offering='Transactions' /> */}
36
- <Link href="/" style={{
37
- textDecoration: 'none',
38
- color: 'inherit'
39
- }}>
40
- <Box sx={{
41
- fontWeight: 'bold'
42
- }}>Microlight</Box>
11
+ return <nav className="px-2 flex items-center justify-between border-b border-border fixed top-0 left-0 right-0 h-10 z-[1000] bg-background">
12
+ <div className="flex items-center gap-4">
13
+ <Link href="/" className="no-underline text-inherit">
14
+ <span className="font-bold">Microlight</span>
43
15
  </Link>
44
16
 
45
- <Box sx={{
46
- display: 'flex',
47
- gap: 1
48
- }}>
49
- <Link href="/library" style={{
50
- textDecoration: 'none'
51
- }}>
52
- <Button variant={pathname?.startsWith('/library') ? 'solid' : 'plain'} size="sm" color="neutral">
17
+ <div className="flex gap-2">
18
+ <Link href="/library" className="no-underline">
19
+ <Button variant={pathname?.startsWith('/library') ? 'default' : 'ghost'} size="sm">
53
20
  Library
54
21
  </Button>
55
22
  </Link>
56
23
 
57
- <Link href="/monitoring" style={{
58
- textDecoration: 'none'
59
- }}>
60
- <Button variant={pathname?.startsWith('/monitoring') ? 'solid' : 'plain'} size="sm" color="neutral">
24
+ <Link href="/monitoring" className="no-underline">
25
+ <Button variant={pathname?.startsWith('/monitoring') ? 'default' : 'ghost'} size="sm">
61
26
  Monitoring
62
27
  </Button>
63
28
  </Link>
64
- </Box>
65
- </Box>
66
-
67
- </Sheet>;
68
- }
69
- ;
29
+ </div>
30
+ </div>
31
+ </nav>;
32
+ }
@@ -1,26 +1,13 @@
1
1
  import Navbar from "./Navbar";
2
- import { Box } from "@mui/joy";
3
2
  export default function NavbarContainer({
4
3
  children
5
4
  }) {
6
5
  return <>
7
- <Navbar />
8
- <Box component="main" sx={{
9
- pt: '40px',
10
- display: 'flex'
11
- }}>
12
- <Box sx={{
13
- flex: 1,
14
- px: {
15
- xs: 1,
16
- sm: 1.5
17
- },
18
- pt: 0.5,
19
- pb: 0,
20
- minWidth: 0
21
- }}>
22
- {children}
23
- </Box>
24
- </Box>
25
- </>;
6
+ <Navbar />
7
+ <main className="pt-10 flex">
8
+ <div className="flex-1 px-2 sm:px-3 pt-1 pb-0 min-w-0">
9
+ {children}
10
+ </div>
11
+ </main>
12
+ </>;
26
13
  }
@@ -1,31 +1,24 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
- import { Box, Typography, Breadcrumbs } from '@mui/joy';
5
- import { Link as JoyLink } from '@mui/joy';
6
4
  import NextLink from 'next/link';
7
-
8
- // Create a basic link component in switchless and import it here
9
- const BreadcrumbLink = ({
10
- href,
11
- children
12
- }) => {
13
- return <JoyLink color="primary" component={NextLink} href={href}>
14
- {children}
15
- </JoyLink>;
16
- };
5
+ import { Typography } from "./ui/typography";
6
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "./ui/breadcrumb";
17
7
  const BreadcrumbsCustom = function ({
18
8
  breadcrumbs
19
9
  }) {
20
- return <Breadcrumbs separator="›" aria-label="breadcrumbs" sx={{
21
- p: 0
22
- }}>
23
- {breadcrumbs.map((b, index) => <React.Fragment key={index}>
24
- {b.href ? <BreadcrumbLink key={b.href} href={b.href}>
25
- {b.text}
26
- </BreadcrumbLink> : <Typography key={b.text}>{b.text}</Typography>}
27
- </React.Fragment>)}
28
- </Breadcrumbs>;
10
+ return <Breadcrumb>
11
+ <BreadcrumbList>
12
+ {breadcrumbs.map((b, index) => <React.Fragment key={index}>
13
+ {index > 0 && <BreadcrumbSeparator>›</BreadcrumbSeparator>}
14
+ <BreadcrumbItem>
15
+ {b.href ? <BreadcrumbLink asChild>
16
+ <NextLink href={b.href}>{b.text}</NextLink>
17
+ </BreadcrumbLink> : <BreadcrumbPage>{b.text}</BreadcrumbPage>}
18
+ </BreadcrumbItem>
19
+ </React.Fragment>)}
20
+ </BreadcrumbList>
21
+ </Breadcrumb>;
29
22
  };
30
23
  export default function PageHeader({
31
24
  header = "PageHeader",
@@ -37,51 +30,26 @@ export default function PageHeader({
37
30
  if (headerLevel) level = headerLevel;
38
31
  const renderHeader = () => {
39
32
  if (React.isValidElement(header)) {
40
- return header; // Return React component as is
33
+ return header;
41
34
  } else if (typeof header === 'string') {
42
35
  return <Typography level={level}>{header}</Typography>;
43
36
  } else if (typeof header === 'object') {
44
37
  const headerParts = Object.values(header);
45
38
  return <Typography level={level}>
46
- {headerParts.length === 1 ? <span>
47
- {headerParts[0]}
48
- </span> : headerParts.map((part, index) => <span key={index} style={{
49
- opacity: index === 1 ? 1 : 0.5
50
- }}>
39
+ {headerParts.length === 1 ? <span>{headerParts[0]}</span> : headerParts.map((part, index) => <span key={index} className={index === 1 ? 'opacity-100' : 'opacity-50'}>
51
40
  {part}{index < headerParts.length - 1 && ' '}
52
41
  </span>)}
53
42
  </Typography>;
54
43
  }
55
44
  return null;
56
45
  };
57
- return <Box data-cy='page-header' sx={{
58
- display: 'flex',
59
- flexDirection: {
60
- xs: 'column',
61
- sm: 'row'
62
- },
63
- alignItems: {
64
- xs: 'flex-start',
65
- sm: 'center'
66
- },
67
- gap: {
68
- xs: 1,
69
- sm: 1
70
- },
71
- pt: 0.5
72
- }}>
73
- <Box sx={{
74
- flexGrow: 1
75
- }}>
46
+ return <div data-cy='page-header' className="flex flex-col sm:flex-row items-start sm:items-center gap-2 pt-1">
47
+ <div className="flex-grow">
76
48
  {breadcrumbs && <BreadcrumbsCustom breadcrumbs={breadcrumbs} />}
77
49
  {renderHeader()}
78
- </Box>
79
- {RightButtons && <Box sx={{
80
- flexGrow: 0,
81
- width: 'auto',
82
- margin: "auto 0"
83
- }}>
50
+ </div>
51
+ {RightButtons && <div className="flex-shrink-0 my-auto">
84
52
  {typeof RightButtons === 'function' ? <RightButtons /> : RightButtons}
85
- </Box>}
86
- </Box>;
53
+ </div>}
54
+ </div>;
87
55
  }
@@ -1,11 +1,11 @@
1
- import { Chip } from '@mui/joy';
2
- function chipStatusColor(status) {
3
- if (status === 'complete') return 'success';else if (status === 'pending') return 'warning';else if (status === 'running') return 'primary';else return 'danger';
1
+ import { Badge } from "./ui/badge";
2
+ function chipStatusVariant(status) {
3
+ if (status === 'complete') return 'success';else if (status === 'pending') return 'warning';else if (status === 'running') return 'default';else return 'destructive';
4
4
  }
5
5
  export default function StatusChip({
6
6
  status
7
7
  }) {
8
- return <Chip variant="soft" color={chipStatusColor(status)} size="sm">
8
+ return <Badge variant={chipStatusVariant(status)}>
9
9
  {status || 'pending'}
10
- </Chip>;
10
+ </Badge>;
11
11
  }
@@ -0,0 +1,61 @@
1
+ // @ts-check
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../utils/css/cn";
5
+ const alertVariants = cva("relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", {
6
+ variants: {
7
+ variant: {
8
+ default: "bg-card text-card-foreground border-border",
9
+ destructive: "text-destructive bg-destructive/10 border-destructive/20 [&>svg]:text-destructive *:data-[slot=alert-description]:text-destructive/90",
10
+ warning: "text-amber-600 bg-amber-50 border-amber-200 [&>svg]:text-amber-600 *:data-[slot=alert-description]:text-amber-600/90 dark:text-amber-400 dark:bg-amber-950/20 dark:border-amber-800/20 dark:[&>svg]:text-amber-400",
11
+ success: "text-green-600 bg-green-50 border-green-200 [&>svg]:text-green-600 *:data-[slot=alert-description]:text-green-600/90 dark:text-green-400 dark:bg-green-950/20 dark:border-green-800/20 dark:[&>svg]:text-green-400"
12
+ }
13
+ },
14
+ defaultVariants: {
15
+ variant: "default"
16
+ }
17
+ });
18
+
19
+ /**
20
+ * @param {{
21
+ * className?: string,
22
+ * variant?: 'default' | 'destructive' | 'warning' | 'success'
23
+ * } & React.HTMLAttributes<HTMLDivElement>} props
24
+ * @returns {React.JSX.Element}
25
+ */
26
+ function Alert({
27
+ className,
28
+ variant,
29
+ ...props
30
+ }) {
31
+ return <div data-slot="alert" role="alert" className={cn(alertVariants({
32
+ variant
33
+ }), className)} {...props} />;
34
+ }
35
+
36
+ /**
37
+ * @param {{
38
+ * className?: string
39
+ * } & React.HTMLAttributes<HTMLDivElement>} props
40
+ * @returns {React.JSX.Element}
41
+ */
42
+ function AlertTitle({
43
+ className,
44
+ ...props
45
+ }) {
46
+ return <div data-slot="alert-title" className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)} {...props} />;
47
+ }
48
+
49
+ /**
50
+ * @param {{
51
+ * className?: string
52
+ * } & React.HTMLAttributes<HTMLDivElement>} props
53
+ * @returns {React.JSX.Element}
54
+ */
55
+ function AlertDescription({
56
+ className,
57
+ ...props
58
+ }) {
59
+ return <div data-slot="alert-description" className={cn("text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", className)} {...props} />;
60
+ }
61
+ export { Alert, AlertTitle, AlertDescription };
@@ -0,0 +1,37 @@
1
+ // @ts-check
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../utils/css/cn";
5
+ const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", {
6
+ variants: {
7
+ variant: {
8
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
9
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
10
+ destructive: "border-transparent bg-red-100 text-red-800 hover:bg-red-200",
11
+ outline: "text-foreground",
12
+ warning: "border-transparent bg-yellow-100 text-yellow-800 hover:bg-yellow-200",
13
+ success: "border-transparent bg-green-100 text-green-800 hover:bg-green-200"
14
+ }
15
+ },
16
+ defaultVariants: {
17
+ variant: "default"
18
+ }
19
+ });
20
+
21
+ /**
22
+ * @param {{
23
+ * className?: string,
24
+ * variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'warning' | 'success'
25
+ * } & React.HTMLAttributes<HTMLDivElement>} props
26
+ * @returns {React.JSX.Element}
27
+ */
28
+ function Badge({
29
+ className,
30
+ variant,
31
+ ...props
32
+ }) {
33
+ return <div className={cn(badgeVariants({
34
+ variant
35
+ }), className)} {...props} />;
36
+ }
37
+ export { Badge, badgeVariants };
@@ -0,0 +1,82 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
+ import { cn } from "../../utils/css/cn";
5
+
6
+ /**
7
+ * @param {React.ComponentProps<"nav">} props
8
+ */
9
+ function Breadcrumb({
10
+ ...props
11
+ }) {
12
+ return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
13
+ }
14
+
15
+ /**
16
+ * @param {React.ComponentProps<"ol">} props
17
+ */
18
+ function BreadcrumbList({
19
+ className,
20
+ ...props
21
+ }) {
22
+ return <ol data-slot="breadcrumb-list" className={cn("text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", className)} {...props} />;
23
+ }
24
+
25
+ /**
26
+ * @param {React.ComponentProps<"li">} props
27
+ */
28
+ function BreadcrumbItem({
29
+ className,
30
+ ...props
31
+ }) {
32
+ return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />;
33
+ }
34
+
35
+ /**
36
+ * @param {React.ComponentProps<"a"> & { asChild?: boolean }} props
37
+ */
38
+ function BreadcrumbLink({
39
+ asChild,
40
+ className,
41
+ ...props
42
+ }) {
43
+ const Comp = asChild ? Slot : "a";
44
+ return <Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} />;
45
+ }
46
+
47
+ /**
48
+ * @param {React.ComponentProps<"span">} props
49
+ */
50
+ function BreadcrumbPage({
51
+ className,
52
+ ...props
53
+ }) {
54
+ return <span data-slot="breadcrumb-page" role="link" aria-disabled="true" aria-current="page" className={cn("text-foreground font-normal", className)} {...props} />;
55
+ }
56
+
57
+ /**
58
+ * @param {React.ComponentProps<"li">} props
59
+ */
60
+ function BreadcrumbSeparator({
61
+ children,
62
+ className,
63
+ ...props
64
+ }) {
65
+ return <li data-slot="breadcrumb-separator" role="presentation" aria-hidden="true" className={cn("[&>svg]:size-3.5", className)} {...props}>
66
+ {children ?? <ChevronRight />}
67
+ </li>;
68
+ }
69
+
70
+ /**
71
+ * @param {React.ComponentProps<"span">} props
72
+ */
73
+ function BreadcrumbEllipsis({
74
+ className,
75
+ ...props
76
+ }) {
77
+ return <span data-slot="breadcrumb-ellipsis" role="presentation" aria-hidden="true" className={cn("flex size-9 items-center justify-center", className)} {...props}>
78
+ <MoreHorizontal className="size-4" />
79
+ <span className="sr-only">More</span>
80
+ </span>;
81
+ }
82
+ export { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis };
@@ -0,0 +1,65 @@
1
+ // @ts-check
2
+ import * as React from "react";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import { cva } from "class-variance-authority";
5
+ import { Loader2 } from "lucide-react";
6
+ import { cn } from "../../utils/css/cn";
7
+ const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
11
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
12
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
13
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
14
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
15
+ link: "text-primary underline-offset-4 hover:underline"
16
+ },
17
+ size: {
18
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
19
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
20
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
21
+ icon: "size-9"
22
+ }
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ size: "default"
27
+ }
28
+ });
29
+
30
+ /**
31
+ * @param {{
32
+ * className?: string,
33
+ * variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link',
34
+ * size?: 'default' | 'sm' | 'lg' | 'icon',
35
+ * asChild?: boolean,
36
+ * loading?: boolean,
37
+ * disabled?: boolean,
38
+ * children?: React.ReactNode
39
+ * } & React.ButtonHTMLAttributes<HTMLButtonElement>} props
40
+ * @returns {React.JSX.Element}
41
+ */
42
+ function Button({
43
+ className,
44
+ variant,
45
+ size,
46
+ asChild = false,
47
+ loading = false,
48
+ children,
49
+ disabled,
50
+ ...props
51
+ }) {
52
+ const Comp = asChild ? Slot : "button";
53
+ const content = <>
54
+ {loading && <Loader2 className="h-4 w-4 animate-spin" />}
55
+ {children}
56
+ </>;
57
+ return <Comp data-slot="button" className={cn(buttonVariants({
58
+ variant,
59
+ size,
60
+ className
61
+ }))} disabled={disabled || loading} {...props}>
62
+ {asChild ? <span className="inline-flex items-center gap-2">{content}</span> : content}
63
+ </Comp>;
64
+ }
65
+ export { Button, buttonVariants };