Chore: Remove the v0 frontend (#2611)

* feat: remove all v0 paths

* chore: run knip

* chore: knip, allow removals

* chore: rm old components

* fix: gen

* fix: rm redirect

* fix: rm some v0 stuff

* fix: rm more unused stuff

* fix: rm more

* fix: more unused stuff

* chore: gen

* fix: redirect from root

* fix: revert doc changes
This commit is contained in:
matt
2025-12-12 10:26:56 -05:00
committed by GitHub
parent aa873e20ce
commit a6804b33b6
278 changed files with 334 additions and 30102 deletions
+1 -31
View File
@@ -22,7 +22,6 @@
"@lukemorales/query-key-factory": "^1.3.4",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
@@ -31,10 +30,8 @@
"@radix-ui/react-hover-card": "^1.1.6",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-menubar": "^1.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
@@ -42,36 +39,17 @@
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.8",
"@rjsf/core": "^5.24.8",
"@rjsf/utils": "^5.24.8",
"@rjsf/validator-ajv8": "^5.24.8",
"@sentry/react": "^7.120.3",
"@sentry/vite-plugin": "^2.23.0",
"@tanstack/react-query": "^5.71.1",
"@tanstack/react-query-devtools": "^5.74.6",
"@tanstack/react-table": "^8.21.2",
"@types/lodash": "^4.17.20",
"@visx/axis": "^3.12.0",
"@visx/brush": "^3.12.0",
"@visx/curve": "^3.12.0",
"@visx/event": "^3.12.0",
"@visx/gradient": "^3.12.0",
"@visx/grid": "^3.12.0",
"@visx/group": "^3.12.0",
"@visx/mock-data": "^3.12.0",
"@visx/pattern": "^3.12.0",
"@visx/responsive": "^3.12.0",
"@visx/scale": "^3.12.0",
"@visx/shape": "^3.12.0",
"@visx/text": "^3.12.0",
"@visx/tooltip": "^3.12.0",
"@visx/vendor": "^3.12.0",
"ansi-to-html": "^0.7.2",
"axios": "^1.12.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^0.2.1",
"cron-parser": "^4.9.0",
"cronstrue": "^2.57.0",
"dagre": "^0.8.5",
"date-fns": "^3.6.0",
@@ -82,8 +60,6 @@
"lucide-react": "^0.446.0",
"monaco-themes": "^0.4.4",
"posthog-js": "^1.298.1",
"prism-react-renderer": "^2.4.1",
"prismjs": "^1.30.0",
"qs": "^6.14.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
@@ -94,7 +70,6 @@
"react-syntax-highlighter": "^15.6.1",
"reactflow": "^11.11.4",
"recharts": "^2.15.1",
"shiki": "^3.2.2",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.7",
@@ -104,7 +79,6 @@
},
"devDependencies": {
"@types/dagre": "^0.7.52",
"@types/dompurify": "^3.2.0",
"@types/node": "^20.17.28",
"@types/qs": "^6.9.18",
"@types/react": "^18.3.20",
@@ -117,7 +91,6 @@
"eslint": "^8.57.1",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.10.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.5",
"eslint-plugin-react": "^7.37.4",
@@ -126,13 +99,10 @@
"eslint-plugin-unused-imports": "^3.2.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"swagger-typescript-api": "^13.0.28",
"tailwindcss": "^3.4.17",
"ts-unused-exports": "^11.0.1",
"tsx": "^4.19.3",
"typescript": "^5.8.2",
"vite": "^6.3.6",
"vite-plugin-eslint": "^1.8.1"
"vite": "^6.3.6"
},
"pnpm": {
"overrides": {
+13 -1861
View File
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
export * from './payment-methods';
export * from './subscription';
@@ -1,111 +0,0 @@
import { Button } from '@/components/ui/button';
import {
FaCcAmex,
FaCcDiscover,
FaCcMastercard,
FaCcVisa,
FaCreditCard,
FaCcDinersClub,
FaCcJcb,
} from 'react-icons/fa';
import { LuBanknote } from 'react-icons/lu';
import { IconType } from 'react-icons/lib';
import { TenantContextType } from '@/lib/outlet';
import { useOutletContext } from 'react-router-dom';
import { useApiError } from '@/lib/hooks';
import { useState } from 'react';
import { Spinner } from '@/components/ui/loading';
import { TenantPaymentMethod } from '@/lib/api/generated/cloud/data-contracts';
import { cloudApi } from '@/lib/api/api';
const ccIcons: Record<string, IconType> = {
visa: FaCcVisa,
mastercard: FaCcMastercard,
amex: FaCcAmex,
discover: FaCcDiscover,
dinersclub: FaCcDinersClub,
jcb: FaCcJcb,
generic: FaCreditCard,
link: LuBanknote,
};
export interface PaymentMethodsProps {
hasMethods?: boolean;
methods?: TenantPaymentMethod[];
}
export function PaymentMethods({
methods = [],
hasMethods,
}: PaymentMethodsProps) {
const { tenant } = useOutletContext<TenantContextType>();
const { handleApiError } = useApiError({});
const [loading, setLoading] = useState(false);
const manageClicked = async () => {
try {
setLoading(true);
const link = await cloudApi.billingPortalLinkGet(tenant.metadata.id);
if (link.data.url) {
window.location.href = link.data.url;
}
} catch (e) {
handleApiError(e as any);
} finally {
setLoading(false);
}
};
return (
<>
<div className="mx-auto py-8 px-4 sm:px-6 lg:px-8">
<div className="flex flex-row justify-between items-center">
<h3 className="text-xl font-semibold leading-tight text-foreground">
Payment Methods
</h3>
</div>
{hasMethods ? (
<>
{methods.map((method, i) => {
const Icon =
method.brand in ccIcons
? ccIcons[method.brand]
: ccIcons.generic;
return (
<div key={i} className="flex flex-row items-center gap-4 mb-4">
<div className="flex flex-col mt-4 text-sm">
<div className="flex flex-row gap-2 items-center">
<Icon size={24} />
{method.brand.toUpperCase()}
{method.last4 && ` *** *** ${method.last4} `}
{method.expiration && `(Expires ${method.expiration})`}
</div>
</div>
</div>
);
})}
<div className="mt-4">
<Button onClick={manageClicked} variant="default">
{loading ? <Spinner /> : 'Manage Payment Methods'}
</Button>
</div>
</>
) : (
<div className="mt-4">
<p className="">
No payment methods added. Payment method is required to upgrade
your subscription.
</p>
<div className="mt-4">
<Button onClick={manageClicked} variant="default">
{loading ? <Spinner /> : 'Add a Payment Method'}
</Button>
</div>
</div>
)}
</div>
</>
);
}
@@ -1,270 +0,0 @@
import { ConfirmDialog } from '@/components/molecules/confirm-dialog';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Spinner } from '@/components/ui/loading';
import { Switch } from '@/components/ui/switch';
import { queries } from '@/lib/api';
import { cloudApi } from '@/lib/api/api';
import {
TenantSubscription,
SubscriptionPlan,
Coupon,
} from '@/lib/api/generated/cloud/data-contracts';
import { useApiError } from '@/lib/hooks';
import { TenantContextType } from '@/lib/outlet';
import queryClient from '@/query-client';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useOutletContext } from 'react-router-dom';
interface SubscriptionProps {
active?: TenantSubscription;
plans?: SubscriptionPlan[];
hasPaymentMethods?: boolean;
coupons?: Coupon[];
}
export const Subscription: React.FC<SubscriptionProps> = ({
active,
plans,
coupons,
hasPaymentMethods,
}) => {
// Implement the logic for the Subscription component here
const [loading, setLoading] = useState<string>();
const [showAnnual, setShowAnnual] = useState<boolean>(false);
const [isChangeConfirmOpen, setChangeConfirmOpen] = useState<
SubscriptionPlan | undefined
>(undefined);
const { tenant } = useOutletContext<TenantContextType>();
const { handleApiError } = useApiError({});
const [portalLoading, setPortalLoading] = useState(false);
const manageClicked = async () => {
try {
if (portalLoading) {
return;
}
setPortalLoading(true);
const link = await cloudApi.billingPortalLinkGet(tenant.metadata.id);
window.open(link.data.url, '_blank');
} catch (e) {
handleApiError(e as any);
} finally {
setPortalLoading(false);
}
};
const subscriptionMutation = useMutation({
mutationKey: ['user:update:logout'],
mutationFn: async ({ plan_code }: { plan_code: string }) => {
const [plan, period] = plan_code.split(':');
setLoading(plan_code);
await cloudApi.subscriptionUpsert(tenant.metadata.id, { plan, period });
},
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({
queryKey: queries.tenantResourcePolicy.get(tenant.metadata.id)
.queryKey,
}),
queryClient.invalidateQueries({
queryKey: queries.cloud.billing(tenant.metadata.id).queryKey,
}),
]);
setLoading(undefined);
},
onError: handleApiError,
});
const activePlanCode = useMemo(
() =>
active?.plan
? [active.plan, active.period].filter((x) => !!x).join(':')
: 'free',
[active],
);
useEffect(() => {
return setShowAnnual(active?.period?.includes('yearly') || false);
}, [active]);
const sortedPlans = useMemo(() => {
return plans
?.filter(
(v) =>
v.plan_code === 'free' ||
(showAnnual
? v.period?.includes('yearly')
: v.period?.includes('monthly')),
)
.sort((a, b) => a.amount_cents - b.amount_cents);
}, [plans, showAnnual]);
const isUpgrade = useCallback(
(plan: SubscriptionPlan) => {
if (!active) {
return true;
}
const activePlan = sortedPlans?.find(
(p) => p.plan_code === activePlanCode,
);
const activeAmount = activePlan?.amount_cents || 0;
return plan.amount_cents > activeAmount;
},
[active, activePlanCode, sortedPlans],
);
return (
<>
<ConfirmDialog
isOpen={!!isChangeConfirmOpen}
title={'Confirm Change Plan'}
submitVariant="default"
description={
<>
Are you sure you'd like to change to {isChangeConfirmOpen?.name}{' '}
plan?
<br />
<br />
Upgrades will be prorated and downgrades will take effect at the end
of the billing period.
</>
}
submitLabel={'Change Plan'}
onSubmit={async () => {
await subscriptionMutation.mutateAsync({
plan_code: isChangeConfirmOpen!.plan_code,
});
setLoading(undefined);
setChangeConfirmOpen(undefined);
}}
onCancel={() => setChangeConfirmOpen(undefined)}
isLoading={!!loading}
/>
<div className="mx-auto py-8 px-4 sm:px-6 lg:px-8">
<div className="flex flex-row justify-between items-center">
<h3 className="text-xl font-semibold leading-tight text-foreground flex flex-row gap-2">
Subscription
{coupons?.map((coupon, i) => (
<Badge key={`c${i}`} variant="successful">
{coupon.name} coupon applied
</Badge>
))}
</h3>
<div className="flex gap-2">
<Switch
id="sa"
checked={showAnnual}
onClick={() => {
setShowAnnual((checkedState) => !checkedState);
}}
/>
<Label htmlFor="sa" className="text-sm">
Annual Billing{' '}
<Badge variant="inProgress" className="ml-2">
Save up to 20%
</Badge>
</Label>
</div>
</div>
<p className="text-gray-700 dark:text-gray-300 my-4">
For plan details, please visit{' '}
<a
href="https://hatchet.run/pricing"
className="underline"
target="_blank"
rel="noreferrer"
>
our pricing page
</a>{' '}
or{' '}
<a href="https://hatchet.run/office-hours" className="underline">
contact us
</a>{' '}
if you have custom requirements.
</p>
{!hasPaymentMethods && (
<Alert variant="warn" className="mb-4">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
No Payment Method.
</AlertTitle>
<AlertDescription>
A payment method is required to upgrade your subscription, please{' '}
<a onClick={manageClicked} className="underline pointer" href="#">
add one
</a>{' '}
first.
</AlertDescription>
</Alert>
)}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{sortedPlans?.map((plan, i) => (
<Card className="bg-muted/30 gap-4 flex-col flex" key={i}>
<CardHeader>
<CardTitle className="tracking-wide text-sm">
{plan.name}
</CardTitle>
<CardDescription className="py-4">
$
{(
plan.amount_cents /
100 /
(plan.period == 'yearly' ? 12 : 1)
).toLocaleString()}{' '}
per month billed {plan.period}*
</CardDescription>
<CardDescription>
<Button
disabled={
!hasPaymentMethods ||
plan.plan_code === activePlanCode ||
loading === plan.plan_code
}
variant={
plan.plan_code !== activePlanCode ? 'default' : 'outline'
}
onClick={() => setChangeConfirmOpen(plan)}
>
{loading === plan.plan_code ? (
<Spinner />
) : plan.plan_code === activePlanCode ? (
'Active'
) : isUpgrade(plan) ? (
'Upgrade'
) : (
'Downgrade'
)}
</Button>
</CardDescription>
</CardHeader>
</Card>
))}
</div>
{active?.note && <p className="mt-4">{active?.note}</p>}
<p className="text-sm text-gray-500 mt-4">
* subscription fee billed upfront {showAnnual ? 'yearly' : 'monthly'},
overages billed at the end of each month for usage in that month
</p>
</div>
</>
);
};
@@ -1,218 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import AnsiToHtml from 'ansi-to-html';
import DOMPurify from 'dompurify';
const convert = new AnsiToHtml({
newline: true,
bg: 'transparent',
});
export interface ExtendedLogLine {
badge?: React.ReactNode;
/** @format date-time */
timestamp?: string;
instance?: string;
line: string;
}
type LogProps = {
logs: ExtendedLogLine[];
onTopReached: () => void;
onBottomReached: () => void;
autoScroll?: boolean;
};
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
};
const LoggingComponent: React.FC<LogProps> = ({
logs,
onTopReached,
onBottomReached,
autoScroll = true,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const [refreshing, setRefreshing] = useState(false);
const [lastTopCall, setLastTopCall] = useState<number>(0);
const [lastBottomCall, setLastBottomCall] = useState<number>(0);
const [firstMount, setFirstMount] = useState<boolean>(true);
const previousScrollHeightRef = useRef<number>(0);
const handleScroll = () => {
if (!containerRef.current) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
previousScrollHeightRef.current = scrollHeight;
const now = Date.now();
if (scrollTop === 0 && now - lastTopCall >= 1000) {
if (logs.length > 0) {
onTopReached();
}
setLastTopCall(now);
} else if (
scrollTop + clientHeight >= scrollHeight &&
now - lastBottomCall >= 1000
) {
if (logs.length > 0) {
onBottomReached();
}
setLastBottomCall(now);
}
};
useEffect(() => {
setTimeout(() => {
const container = containerRef.current;
if (container && container.scrollHeight > container.clientHeight) {
if (firstMount && autoScroll) {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
});
setFirstMount(false);
}
}
}, 250);
}, [containerRef, firstMount, autoScroll]);
useEffect(() => {
if (refreshing) {
const timer = setTimeout(() => {
setRefreshing(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [refreshing]);
useEffect(() => {
if (!autoScroll) {
return;
}
const container = containerRef.current;
if (!container) {
return;
}
const previousScrollHeight = previousScrollHeightRef.current;
const currentScrollHeight = container.scrollHeight;
const { scrollTop, clientHeight } = container;
const isAtBottom = scrollTop + clientHeight >= previousScrollHeight;
if (!isAtBottom) {
const newScrollTop =
scrollTop + (currentScrollHeight - previousScrollHeight);
container.scrollTo({ top: newScrollTop });
} else {
container.scrollTo({ top: currentScrollHeight, behavior: 'smooth' });
}
}, [logs, autoScroll]);
const showLogs =
logs.length > 0
? logs
: [
{
line: 'Waiting for logs...',
timestamp: new Date().toISOString(),
instance: 'Hatchet',
},
];
const sortedLogs = [...showLogs].sort((a, b) => {
if (!a.timestamp || !b.timestamp) {
return 0;
}
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
});
return (
<div
className="w-full mx-auto overflow-y-auto p-6 text-indigo-300 font-mono text-xs rounded-md max-h-[25rem] min-h-[25rem] bg-muted scrollbar-thin scrollbar-track-muted scrollbar-thumb-muted-foreground"
ref={containerRef}
onScroll={handleScroll}
>
{refreshing && (
<div className="absolute top-0 left-0 right-0 bg-gray-800 text-white p-2 text-center">
Refreshing...
</div>
)}
{sortedLogs.map((log, i) => {
const sanitizedHtml = DOMPurify.sanitize(
convert.toHtml(log.line || ''),
{
USE_PROFILES: { html: true },
},
);
const logHash = log.timestamp + generateHash(log.line);
return (
<p
key={logHash}
className="pb-2 break-all overflow-x-hidden whitespace-pre-wrap"
id={'log' + i}
>
{log.badge}
{log.timestamp && (
<span className="text-gray-500 mr-2 ml--2">
{new Date(log.timestamp)
.toLocaleString('sv', options)
.replace(',', '.')
.replace(' ', 'T')}
</span>
)}
{log.instance && (
<span className="text-foreground dark:text-white mr-2 ml--2">
{log.instance}
</span>
)}
<span
dangerouslySetInnerHTML={{
__html: sanitizedHtml,
}}
/>
</p>
);
})}
</div>
);
};
const generateHash = (input: string | undefined): string => {
if (!input) {
return Math.random().toString(36).substring(2, 15);
}
const trimmedInput = input.substring(0, 50);
return cyrb53(trimmedInput) + '';
};
// source: https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
const cyrb53 = function (str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
export default LoggingComponent;
@@ -1,6 +1,6 @@
import * as React from 'react';
import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
import type { ToastActionElement, ToastProps } from '@/components/v1/ui/toast';
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
@@ -69,7 +69,7 @@ const addToRemoveQueue = (toastId: string) => {
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
@@ -188,4 +188,4 @@ function useToast() {
};
}
export { useToast, toast };
export { useToast };
@@ -1,360 +0,0 @@
import React, { useMemo, useCallback } from 'react';
import { Group } from '@visx/group';
import { AreaClosed, Line, Bar } from '@visx/shape';
import {
withTooltip,
TooltipWithBounds,
Tooltip,
defaultStyles,
} from '@visx/tooltip';
import { GridRows, GridColumns } from '@visx/grid';
import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip';
import { scaleTime, scaleLinear } from '@visx/scale';
import { AxisLeft, AxisBottom } from '@visx/axis';
import { LinearGradient } from '@visx/gradient';
import { curveMonotoneX } from '@visx/curve';
import { localPoint } from '@visx/event';
import { max, extent, bisector } from '@visx/vendor/d3-array';
import { timeFormat } from '@visx/vendor/d3-time-format';
import { Text } from '@visx/text';
const getDate = (d: MetricValue) => d.date;
const getValue = (d: MetricValue) => d.value;
// format to 2 decimal places
export const format2Dec = (d: number) => {
if (!d.toFixed) {
return '0.00';
}
return `${d.toFixed(2)}`;
};
const bisectDate = bisector<MetricValue, Date>((d) => d.date).left;
export interface MetricValue {
date: Date;
value: number;
}
type TooltipData = MetricValue;
const formatDate = timeFormat('%y-%m-%d %I:%M:%S');
const accentColor = '#ffffff44';
const background = '#1E293B';
const background2 = '#8c77e0';
const accentColorDark = '#8c77e0';
const tooltipStyles = {
...defaultStyles,
border: '1px solid white',
color: 'white',
background,
};
const axisColor = '#cecece';
const axisBottomTickLabelProps = {
textAnchor: 'middle' as const,
fontFamily: 'Arial',
fontSize: 10,
fill: axisColor,
};
const axisLeftTickLabelProps = {
dx: '-0.25em',
dy: '0.25em',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'end' as const,
fill: axisColor,
};
export const formatPercentTooltip = (d: number) => `${format2Dec(d)}%`;
type AreaChartProps = {
data: MetricValue[];
kind: 'area' | 'bar';
gradientColor?: string;
width: number;
height: number;
hideBottomAxis?: boolean;
hideLeftAxis?: boolean;
children?: React.ReactNode;
yLabel?: string;
xLabel?: string;
yDomain?: [number, number];
xDomain?: [Date, Date];
centerText?: string;
tooltipFormat?: (d: number) => string;
};
export default withTooltip<AreaChartProps, TooltipData>(
({
data,
kind,
gradientColor = background2,
width,
height,
hideBottomAxis = false,
hideLeftAxis = false,
children,
yLabel,
xLabel,
yDomain,
xDomain,
centerText,
showTooltip,
hideTooltip,
tooltipFormat,
tooltipData,
tooltipTop = 0,
tooltipLeft = 0,
}: AreaChartProps & WithTooltipProvidedProps<TooltipData>) => {
if (width < 10) {
return null;
}
const innerWidth = width;
const innerHeight = height;
const dateScale = useMemo(
() =>
scaleTime<number>({
range: [0, width],
domain: xDomain || (extent(data, getDate) as [Date, Date]),
}),
[width, data, xDomain],
);
const yScale = useMemo(
() =>
scaleLinear<number>({
range: [height, 0],
domain: yDomain || [0, 1.3 * (max(data, getValue) || 0)],
nice: true,
}),
[height, data, yDomain],
);
const handleTooltip = useCallback(
(
event:
| React.TouchEvent<SVGRectElement>
| React.MouseEvent<SVGRectElement>,
) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = dateScale.invert(x);
const index = bisectDate(data, x0, 1);
const d0 = data[index - 1];
const d1 = data[index];
let d = d0;
if (d1 && getDate(d1)) {
d =
x0.valueOf() - getDate(d0).valueOf() >
getDate(d1).valueOf() - x0.valueOf()
? d1
: d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: yScale(getValue(d)),
});
},
[showTooltip, yScale, dateScale, data],
);
let barWidth = innerWidth / data.length;
if (barWidth <= 5) {
barWidth = 6;
}
return (
<div>
<svg width={width} height={height} overflow={'visible'}>
{centerText && (
<Text className="fill-foreground" x="50%" y="50%" dx={-200}>
{centerText}
</Text>
)}
<rect
x={0}
y={0}
width={innerWidth}
height={innerHeight}
fill="url(#area-background-gradient)"
rx={14}
/>
<GridRows
left={0}
scale={yScale}
width={innerWidth}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.6}
pointerEvents="none"
/>
<GridColumns
top={0}
left={0}
scale={dateScale}
width={innerWidth}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.6}
pointerEvents="none"
/>
<Group height={height} width={width}>
<LinearGradient
id="gradient"
from={gradientColor}
fromOpacity={1}
to={gradientColor}
toOpacity={0.2}
height={innerHeight}
/>
{kind == 'bar' &&
data.map((d, i) => {
if (i == 0) {
return (
<Bar
key={i}
x={dateScale(getDate(d)) || 0}
y={yScale(getValue(d)) || 0}
width={(barWidth - 4) / 2}
height={innerHeight - yScale(getValue(d)) || 0}
fill="url(#gradient)"
rx={2}
/>
);
}
return (
<Bar
key={i}
x={(dateScale(getDate(d)) || 0) - barWidth / 2}
y={yScale(getValue(d)) || 0}
width={barWidth - 4}
height={innerHeight - yScale(getValue(d)) || 0}
fill="url(#gradient)"
rx={2}
/>
);
})}
{kind == 'area' && (
<AreaClosed<MetricValue>
data={data}
x={(d) => dateScale(d.date) || 0}
y={(d) => yScale(d.value) || 0}
yScale={yScale}
strokeWidth={1}
stroke="url(#gradient)"
fill="url(#gradient)"
curve={curveMonotoneX}
height={innerHeight}
/>
)}
{!hideBottomAxis && (
<AxisBottom
top={height}
scale={dateScale}
numTicks={width > 520 ? 10 : 5}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={axisBottomTickLabelProps}
label={xLabel}
/>
)}
{!hideLeftAxis && (
<AxisLeft
scale={yScale}
numTicks={5}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={axisLeftTickLabelProps}
label={yLabel}
labelClassName="text-white fill-foreground"
/>
)}
{children}
</Group>
<Bar
x={0}
y={0}
width={innerWidth}
height={innerHeight}
fill="transparent"
rx={14}
onTouchStart={handleTooltip}
onTouchMove={handleTooltip}
onMouseMove={handleTooltip}
onMouseLeave={() => hideTooltip()}
/>
{data.length > 0 && tooltipData && (
<g>
<Line
from={{ x: tooltipLeft, y: 0 }}
to={{ x: tooltipLeft, y: innerHeight + 0 }}
stroke={accentColorDark}
strokeWidth={2}
pointerEvents="none"
strokeDasharray="5,2"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop + 1}
r={4}
fill="black"
fillOpacity={0.1}
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill={accentColorDark}
stroke="white"
strokeWidth={2}
pointerEvents="none"
/>
</g>
)}
</svg>
{data.length > 0 && tooltipData && (
<div>
<TooltipWithBounds
key={Math.random()}
top={tooltipTop - 24}
left={tooltipLeft}
style={tooltipStyles}
>
{tooltipFormat
? tooltipFormat(getValue(tooltipData))
: getValue(tooltipData)}
</TooltipWithBounds>
<Tooltip
top={innerHeight - 14}
left={tooltipLeft}
style={{
...defaultStyles,
minWidth: 72,
textAlign: 'center',
transform: 'translateX(-50%)',
}}
>
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
)}
</div>
);
},
);
@@ -1,454 +0,0 @@
import { useState, useMemo, useRef } from 'react';
import {
CartesianGrid,
XAxis,
YAxis,
ReferenceArea,
ResponsiveContainer,
Bar,
BarChart,
LineChart,
Line,
Area,
AreaChart,
} from 'recharts';
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '@/components/ui/chart';
import { capitalize, cn } from '@/lib/utils';
export type DataPoint<T extends string> = Record<T, number> & {
date: string;
};
const getNextActiveLabel = (activeLabel: string, data: DataPoint<string>[]) => {
const currentIndex = data.findIndex((d) => d.date === activeLabel);
if (currentIndex === -1) {
return null;
}
// if we're at the end of the data, determine the time between the last two data points and add that to the last date
if (currentIndex === data.length - 1) {
const lastDate = new Date(data[currentIndex].date);
const secondLastDate = new Date(data[currentIndex - 1].date);
const diff = lastDate.getTime() - secondLastDate.getTime();
return new Date(lastDate.getTime() + diff).toISOString();
}
return data[currentIndex + 1]?.date || activeLabel;
};
const getPrevActiveLabel = (activeLabel: string, data: DataPoint<string>[]) => {
const currentIndex = data.findIndex((d) => d.date === activeLabel);
if (currentIndex === -1) {
return activeLabel;
}
// if we're at the start of the data, determine the time between the first two data points and subtract that from the first date
if (currentIndex === 0) {
const firstDate = new Date(data[currentIndex].date);
const secondDate = new Date(data[currentIndex + 1].date);
const diff = secondDate.getTime() - firstDate.getTime();
return new Date(firstDate.getTime() - diff).toISOString();
}
return data[currentIndex - 1]?.date || activeLabel;
};
type ZoomableChartProps<T extends string> = {
data: DataPoint<T>[];
colors?: Record<string, string>;
zoom?: (startTime: string, endTime: string) => void;
showYAxis?: boolean;
kind: 'bar' | 'line' | 'area';
className?: string;
};
export function ZoomableChart<T extends string>({
data,
colors,
zoom,
showYAxis = true,
kind = 'bar',
className,
}: ZoomableChartProps<T>) {
const [refAreaLeft, setRefAreaLeft] = useState<string | null>(null);
const [refAreaRight, setRefAreaRight] = useState<string | null>(null);
const [actualRefAreaLeft, setActualRefAreaLeft] = useState<string | null>(
null,
);
const [actualRefAreaRight, setActualRefAreaRight] = useState<string | null>(
null,
);
const [isSelecting, setIsSelecting] = useState(false);
const chartRef = useRef<HTMLDivElement>(null);
const chartConfig = useMemo<ChartConfig>(() => {
const keys = Object.keys(data[0] || {}).filter((key) => key !== 'date');
return keys.reduce<ChartConfig>((acc, key, index) => {
let color = `hsl(${(index * 360) / keys.length}, 70%, 50%)`;
if (colors && colors[key]) {
color = colors[key];
}
if (index < 5) {
color = `hsl(var(--chart-${index + 1}))`;
}
acc[key] = {
label: capitalize(key),
color: colors?.[key] || color,
};
return acc;
}, {});
}, [data, colors]);
const handleMouseDown = (e: any) => {
if (e.activeLabel) {
setRefAreaLeft(e.activeLabel);
setActualRefAreaLeft(getPrevActiveLabel(e.activeLabel, data));
setIsSelecting(true);
}
};
const handleMouseMove = (e: any) => {
if (isSelecting && e.activeLabel) {
setRefAreaRight(e.activeLabel);
setActualRefAreaRight(getNextActiveLabel(e.activeLabel, data));
}
};
const handleMouseUp = () => {
if (actualRefAreaLeft && actualRefAreaRight) {
const [left, right] = [actualRefAreaLeft, actualRefAreaRight].sort();
zoom?.(left, right);
}
setRefAreaLeft(null);
setActualRefAreaLeft(null);
setRefAreaRight(null);
setActualRefAreaRight(null);
setIsSelecting(false);
};
const minDate = new Date(
Math.min(...data.map((d) => new Date(d.date).getTime())),
);
const maxDate = new Date(
Math.max(...data.map((d) => new Date(d.date).getTime())),
);
const formatXAxis = (tickItem: string) => {
const date = new Date(tickItem);
const timeDiff = maxDate.getTime() - minDate.getTime();
const oneDay = 24 * 60 * 60 * 1000;
const sevenDays = 7 * oneDay;
if (timeDiff > sevenDays) {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
} else if (timeDiff > oneDay) {
return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })} ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
} else {
return date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
}
};
// remove date from dataKeys
const dataKeys = Object.keys(data[0] || {}).filter((key) => key !== 'date');
return (
<ChartContainer
config={chartConfig}
className={cn('w-full h-[200px] min-h-[200px]', className)}
>
<div className="h-full" ref={chartRef} style={{ touchAction: 'none' }}>
{getChildChart(kind, {
data,
showYAxis,
formatXAxis,
handleMouseDown,
handleMouseMove,
handleMouseUp,
refAreaLeft,
refAreaRight,
chartConfig,
dataKeys,
})}
</div>
</ChartContainer>
);
}
function getChildChart<T extends string>(
kind: 'bar' | 'line' | 'area',
props: ChildChartProps<T>,
) {
switch (kind) {
case 'bar':
return <ChildBarChart {...props} />;
case 'line':
return <ChildLineChart {...props} />;
case 'area':
return <ChildAreaChart {...props} />;
}
}
type ChildChartProps<T extends string> = {
data: DataPoint<T>[];
showYAxis?: boolean;
formatXAxis: (tickItem: string) => string;
handleMouseDown: (e: any) => void;
handleMouseMove: (e: any) => void;
handleMouseUp: () => void;
refAreaLeft: string | null;
refAreaRight: string | null;
chartConfig: ChartConfig;
dataKeys: string[];
};
function ChildBarChart<T extends string>({
data,
showYAxis = true,
formatXAxis,
handleMouseDown,
handleMouseMove,
handleMouseUp,
refAreaLeft,
refAreaRight,
chartConfig,
dataKeys,
}: ChildChartProps<T>) {
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={data}
margin={{
left: 0,
right: 0,
top: 0,
bottom: 0,
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickFormatter={formatXAxis}
tickLine={false}
axisLine={false}
tickMargin={4}
minTickGap={16}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
{showYAxis && (
<YAxis
tickLine={false}
axisLine={false}
tickMargin={4}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
)}
<ChartTooltip
content={
<ChartTooltipContent
className="w-[150px] sm:w-[200px] font-mono text-xs sm:text-xs"
labelFormatter={(value) => new Date(value).toLocaleString()}
/>
}
/>
{dataKeys.map((key) => (
<Bar
key={key}
type="monotone"
dataKey={key}
stroke={chartConfig[key].color}
fillOpacity={1}
fill={chartConfig[key].color}
isAnimationActive={false}
/>
))}
{refAreaLeft && refAreaRight && (
<ReferenceArea
x1={refAreaLeft}
x2={refAreaRight}
strokeOpacity={0.3}
fill="hsl(var(--foreground))"
fillOpacity={0.1}
/>
)}
</BarChart>
</ResponsiveContainer>
);
}
function ChildLineChart<T extends string>({
data,
showYAxis = true,
formatXAxis,
handleMouseDown,
handleMouseMove,
handleMouseUp,
refAreaLeft,
refAreaRight,
chartConfig,
dataKeys,
}: ChildChartProps<T>) {
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart
data={data}
margin={{
left: 0,
right: 0,
top: 0,
bottom: 0,
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickFormatter={formatXAxis}
tickLine={false}
axisLine={false}
tickMargin={4}
minTickGap={16}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
{showYAxis && (
<YAxis
tickLine={false}
axisLine={false}
tickMargin={4}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
)}
<ChartTooltip
content={
<ChartTooltipContent
className="w-[150px] sm:w-[200px] font-mono text-xs sm:text-xs"
labelFormatter={(value) => new Date(value).toLocaleString()}
/>
}
/>
{dataKeys.map((key) => {
return (
<Line
key={key}
type="monotone"
dot={false}
dataKey={key}
stroke={chartConfig[key].color}
fillOpacity={1}
fill={chartConfig[key].color}
isAnimationActive={false}
/>
);
})}
{refAreaLeft && refAreaRight && (
<ReferenceArea
x1={refAreaLeft}
x2={refAreaRight}
strokeOpacity={0.3}
fill="hsl(var(--foreground))"
fillOpacity={0.1}
/>
)}
</LineChart>
</ResponsiveContainer>
);
}
function ChildAreaChart<T extends string>({
data,
showYAxis = true,
formatXAxis,
handleMouseDown,
handleMouseMove,
handleMouseUp,
refAreaLeft,
refAreaRight,
chartConfig,
dataKeys,
}: ChildChartProps<T>) {
return (
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={data}
margin={{
left: 0,
right: 0,
top: 0,
bottom: 0,
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickFormatter={formatXAxis}
tickLine={false}
axisLine={false}
tickMargin={4}
minTickGap={16}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
{showYAxis && (
<YAxis
tickLine={false}
axisLine={false}
tickMargin={4}
style={{ fontSize: '10px', userSelect: 'none' }}
/>
)}
<ChartTooltip
content={
<ChartTooltipContent
className="w-[150px] sm:w-[200px] font-mono text-xs sm:text-xs"
labelFormatter={(value) => new Date(value).toLocaleString()}
/>
}
/>
{dataKeys.map((key) => {
return (
<Area
key={key}
type="monotone"
dot={false}
dataKey={key}
stroke={chartConfig[key].color}
fillOpacity={0.6}
fill={chartConfig[key].color}
isAnimationActive={false}
stackId="a"
/>
);
})}
{refAreaLeft && refAreaRight && (
<ReferenceArea
x1={refAreaLeft}
x2={refAreaRight}
strokeOpacity={0.3}
fill="hsl(var(--foreground))"
fillOpacity={0.1}
/>
)}
</AreaChart>
</ResponsiveContainer>
);
}
@@ -1,296 +0,0 @@
import * as React from 'react';
import { CheckIcon, CircleIcon, PlusCircledIcon } from '@radix-ui/react-icons';
import { Column } from '@tanstack/react-table';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from '@/components/ui/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { ToolbarType } from '../data-table/data-table-toolbar';
import { Input } from '@/components/ui/input';
import { BiX } from 'react-icons/bi';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>;
title?: string;
type?: ToolbarType;
options?: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
const keyValuePairSchema = z.object({
key: z.string().min(1, 'Key is required'),
value: z.string().min(1, 'Value is required'),
});
const arrayInputSchema = z.object({
values: z.string().min(1, 'At least one value is required'),
});
type KeyValuePair = z.infer<typeof keyValuePairSchema>;
type ArrayInput = z.infer<typeof arrayInputSchema>;
export function DataTableFacetedFilter<TData, TValue>({
column,
title,
type = ToolbarType.Checkbox,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
return (
<Combobox
values={column?.getFilterValue() as string[]}
title={title}
type={type}
options={options}
setValues={(values) => column?.setFilterValue(values)}
/>
);
}
export function Combobox({
values = [],
title,
icon,
type = ToolbarType.Checkbox,
options,
setValues,
}: {
values?: string[];
icon?: JSX.Element;
title?: string;
type?: ToolbarType;
options?: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
setValues: (selectedValues: string[]) => void;
}) {
const { register, handleSubmit, reset } = useForm<KeyValuePair | ArrayInput>({
resolver: zodResolver(
type === ToolbarType.KeyValue ? keyValuePairSchema : arrayInputSchema,
),
defaultValues:
type === ToolbarType.KeyValue ? { key: '', value: '' } : { values: '' },
});
const submit = (data: KeyValuePair | ArrayInput) => {
if ('key' in data) {
values.push(`${data.key}:${data.value}`);
} else {
data.values.split(',').forEach((value) => values.push(value.trim()));
}
setValues(values);
reset();
};
const remove = (filter: string) => {
const index = values.indexOf(filter);
if (index > -1) {
values.splice(index, 1);
}
setValues(values);
};
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 border-dashed">
{icon || <PlusCircledIcon className="mr-2 h-4 w-4" />}
{title}
{values.length > 0 && (
<>
<Separator orientation="vertical" className="mx-2 h-4" />
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal lg:hidden"
>
{type == ToolbarType.Radio
? // get the label of the value
options?.find(({ value }) => value == values[0])?.label ||
values[0]
: values.length}
</Badge>
<div className="hidden space-x-1 lg:flex">
{values.length > 2 ? (
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal"
>
{values.length} selected
</Badge>
) : (
values.map((option, index) => (
<Badge
key={index}
variant="secondary"
className="rounded-sm px-1 font-normal flex items-center space-x-1"
>
{options?.find(({ value }) => value == option)?.label ||
option}
<Button
variant="ghost"
size="xs"
className="ml-2"
onClick={() => remove(option)}
>
<BiX className="h-3 w-3" />
</Button>
</Badge>
))
)}
</div>
</>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-2" align="start">
{[ToolbarType.Array, ToolbarType.KeyValue].includes(type) && (
<div>
<div className="">
{values.map((filter, index) => (
<Badge
key={index}
variant="secondary"
className="mr-2 mb-2 rounded-sm px-1 font-normal flex items-center space-x-1 pl-2"
>
<span className="grow">{filter}</span>
<Button
variant="ghost"
size="icon"
className="ml-2 shrink-0"
onClick={() => remove(filter)}
>
<BiX className="h-4 w-4" />
</Button>
</Badge>
))}
</div>
<form onSubmit={handleSubmit(submit)}>
{type === ToolbarType.KeyValue ? (
<div className="flex items-center space-x-2 mb-2">
<Input
type="text"
placeholder="Key"
{...register('key')}
className="flex-1"
/>
<Input
type="text"
placeholder="Value"
{...register('value')}
className="flex-1"
/>
</div>
) : (
<div className="mb-2">
<Input
type="text"
placeholder="Enter values (comma-separated)"
{...register('values')}
className="w-full"
/>
</div>
)}
<Button type="submit" className="w-full" size="sm">
Add {title} Filter
</Button>
{values.length > 0 && (
<Button
onClick={() => setValues([])}
className="w-full mt-2"
size="sm"
variant={'ghost'}
>
Reset
</Button>
)}
</form>
</div>
)}
{[ToolbarType.Checkbox, ToolbarType.Radio].includes(type) && (
<Command>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{options?.map((option) => {
const isSelected = values.indexOf(option.value) != -1;
return (
<CommandItem
key={option.value}
onSelect={() => {
if (isSelected) {
values.splice(values.indexOf(option.value), 1);
} else {
if (type == 'radio') {
values = [];
}
values.push(option.value);
}
setValues(values);
}}
>
<div
className={cn(
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
isSelected
? 'bg-primary text-primary-foreground'
: 'opacity-50 [&_svg]:invisible',
)}
>
{type === 'checkbox' ? (
<CheckIcon className={cn('h-4 w-4')} />
) : (
<CircleIcon className={cn('h-4 w-4')} />
)}
</div>
{option.icon && (
<option.icon className="mr-2 h-4 w-4 text-gray-700 dark:text-gray-300" />
)}
<span>{option.label}</span>
</CommandItem>
);
})}
</CommandGroup>
{values.length > 0 && (
<>
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => setValues([])}
className="justify-center text-center"
>
Reset
</CommandItem>
</CommandGroup>
</>
)}
</CommandList>
</Command>
)}
</PopoverContent>
</Popover>
);
}
@@ -1,11 +1,11 @@
import { Button, ButtonProps } from '@/components/ui/button';
import { Spinner } from '@/components/ui/loading.tsx';
import { Button, ButtonProps } from '@/components/v1/ui/button';
import { Spinner } from '@/components/v1/ui/loading.tsx';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
} from '@/components/v1/ui/dialog';
interface ConfirmDialogProps {
title: string;
@@ -1,71 +0,0 @@
import {
ArrowDownIcon,
ArrowUpIcon,
CaretSortIcon,
EyeNoneIcon,
} from '@radix-ui/react-icons';
import { Column } from '@tanstack/react-table';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
interface DataTableColumnHeaderProps<TData, TValue>
extends React.HTMLAttributes<HTMLDivElement> {
column: Column<TData, TValue>;
title: string;
}
export function DataTableColumnHeader<TData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) {
if (!column.getCanSort()) {
return <div className={cn(className, 'text-xs')}>{title}</div>;
}
return (
<div className={cn('flex items-center space-x-2', className)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="-ml-3 h-8 data-[state=open]:bg-accent"
>
<span>{title}</span>
{column.getIsSorted() === 'desc' ? (
<ArrowDownIcon className="ml-2 h-4 w-4" />
) : column.getIsSorted() === 'asc' ? (
<ArrowUpIcon className="ml-2 h-4 w-4" />
) : (
<CaretSortIcon className="ml-2 h-4 w-4" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-gray-600 dark:text-gray-400/70" />
Asc
</DropdownMenuItem>
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-gray-600 dark:text-gray-400/70" />
Desc
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
<EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-gray-600 dark:text-gray-400/70" />
Hide
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
@@ -1,33 +0,0 @@
import * as React from 'react';
import { Column } from '@tanstack/react-table';
import { ToolbarType } from './data-table-toolbar';
import { Combobox } from '../combobox/combobox';
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>;
title?: string;
type?: ToolbarType;
options?: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
export function DataTableFacetedFilter<TData, TValue>({
column,
title,
type = ToolbarType.Checkbox,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
return (
<Combobox
values={column?.getFilterValue() as string[]}
title={title}
type={type}
options={options}
setValues={(values) => column?.setFilterValue(values)}
/>
);
}
@@ -1,121 +0,0 @@
import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
DoubleArrowRightIcon,
} from '@radix-ui/react-icons';
import { Table } from '@tanstack/react-table';
import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Label } from '@radix-ui/react-label';
interface DataTablePaginationProps<TData> {
table: Table<TData>;
onSetPageSize?: (pageSize: number) => void;
showSelectedRows?: boolean;
}
export function DataTablePagination<TData>({
table,
onSetPageSize,
showSelectedRows = true,
}: DataTablePaginationProps<TData>) {
const pagination = table.getState().pagination;
return (
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-gray-600 dark:text-gray-400">
{showSelectedRows && (
<div>
{table.getFilteredSelectedRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
)}
</div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<Label
className="text-sm font-medium text-gray-600 dark:text-gray-400"
htmlFor="rows-per-page"
id="rows-per-page-label"
>
Rows per page
</Label>
<Select
value={`${pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
if (onSetPageSize) {
onSetPageSize(Number(value));
}
}}
>
<SelectTrigger
className="h-8 w-[70px]"
id="rows-per-page"
aria-labelledby="rows-per-page-label"
>
<SelectValue placeholder={pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[50, 100, 200, 500].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {pagination.pageIndex + 1} of {table.getPageCount()}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<DoubleArrowLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<DoubleArrowRightIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
);
}
@@ -1,70 +0,0 @@
import { DotsHorizontalIcon } from '@radix-ui/react-icons';
import { Row } from '@tanstack/react-table';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { IDGetter } from './data-table';
import {
Tooltip,
TooltipProvider,
TooltipTrigger,
TooltipContent,
} from '@/components/ui/tooltip';
interface DataTableRowActionsProps<TData extends IDGetter> {
row: Row<TData>;
actions?: {
label: string;
onClick: (data: TData) => void;
disabled?: boolean | string;
}[];
}
export function DataTableRowActions<TData extends IDGetter>({
row,
actions,
}: DataTableRowActionsProps<TData>) {
if (!actions?.length) {
return null;
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<DotsHorizontalIcon className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[160px]">
{actions?.map((action) => (
<TooltipProvider key={action.label}>
<Tooltip>
<TooltipTrigger className="w-full">
<DropdownMenuItem
onClick={() => action.onClick(row.original)}
disabled={!!action.disabled}
className="w-full"
>
{action.label}
</DropdownMenuItem>
</TooltipTrigger>
{action.disabled && (
<TooltipContent>{action.disabled}</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -1,90 +0,0 @@
import { Cross2Icon } from '@radix-ui/react-icons';
import { Table } from '@tanstack/react-table';
import { Button } from '@/components/ui/button';
import { DataTableViewOptions } from './data-table-view-options';
import { DataTableFacetedFilter } from './data-table-faceted-filter';
import { Input } from '@/components/ui/input.tsx';
import { Spinner } from '@/components/ui/loading';
export interface FilterOption {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}
export enum ToolbarType {
Checkbox = 'checkbox',
Radio = 'radio',
KeyValue = 'key-value',
Array = 'array',
}
export type ToolbarFilters = {
columnId: string;
title: string;
type?: ToolbarType;
options?: FilterOption[];
}[];
interface DataTableToolbarProps<TData> {
table: Table<TData>;
filters: ToolbarFilters;
actions: JSX.Element[];
setSearch?: (search: string) => void;
search?: string;
showColumnToggle?: boolean;
isLoading?: boolean;
}
export function DataTableToolbar<TData>({
table,
filters,
actions,
setSearch,
search,
showColumnToggle,
isLoading = false,
}: DataTableToolbarProps<TData>) {
const isFiltered = table.getState().columnFilters?.length > 0;
return (
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
{setSearch && (
<Input
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="h-8 w-[150px] lg:w-[250px]"
/>
)}
{filters.map((filter) => (
<DataTableFacetedFilter
key={filter.columnId}
column={table.getColumn(filter.columnId)}
title={filter.title}
type={filter.type}
options={filter.options}
/>
))}
{isFiltered && (
<Button
variant="ghost"
onClick={() => table.resetColumnFilters()}
className="h-8 px-2 lg:px-3"
>
Reset
<Cross2Icon className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div className="flex flex-row gap-4 items-center">
{isLoading && <Spinner />}
{actions && actions.length > 0 && actions}
{showColumnToggle && <DataTableViewOptions table={table} />}
</div>
</div>
);
}
@@ -1,57 +0,0 @@
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
import { MixerHorizontalIcon } from '@radix-ui/react-icons';
import { Table } from '@tanstack/react-table';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu';
interface DataTableViewOptionsProps<TData> {
table: Table<TData>;
}
export function DataTableViewOptions<TData>({
table,
}: DataTableViewOptionsProps<TData>) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== 'undefined' && column.getCanHide(),
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -1,272 +0,0 @@
import * as React from 'react';
import {
ColumnDef,
ColumnFiltersState,
OnChangeFn,
PaginationState,
Row,
RowSelectionState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { DataTablePagination } from './data-table-pagination';
import { DataTableToolbar, ToolbarFilters } from './data-table-toolbar';
import { Skeleton } from '@/components/ui/skeleton';
import { cn } from '@/lib/utils';
export interface IDGetter {
metadata: {
id: string;
};
getRow?: () => JSX.Element;
onClick?: () => void;
isExpandable?: boolean;
}
interface DataTableProps<TData extends IDGetter, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
error?: Error | null;
filters: ToolbarFilters;
actions?: JSX.Element[];
sorting?: SortingState;
setSorting?: OnChangeFn<SortingState>;
setSearch?: (search: string) => void;
search?: string;
columnFilters?: ColumnFiltersState;
setColumnFilters?: OnChangeFn<ColumnFiltersState>;
pagination?: PaginationState;
setPagination?: OnChangeFn<PaginationState>;
showSelectedRows?: boolean;
pageCount?: number;
onSetPageSize?: (pageSize: number) => void;
showColumnToggle?: boolean;
columnVisibility?: VisibilityState;
setColumnVisibility?: OnChangeFn<VisibilityState>;
rowSelection?: RowSelectionState;
setRowSelection?: OnChangeFn<RowSelectionState>;
isLoading?: boolean;
enableRowSelection?: boolean;
getRowId?:
| ((
originalRow: TData,
index: number,
parent?: Row<TData> | undefined,
) => string)
| undefined;
manualSorting?: boolean;
manualFiltering?: boolean;
}
interface ExtraDataTableProps {
emptyState?: JSX.Element;
card?: {
containerStyle?: string;
component: React.FC<any> | ((data: any) => JSX.Element);
};
}
export function DataTable<TData extends IDGetter, TValue>({
columns,
error,
data,
filters,
actions = [],
sorting,
setSorting,
setSearch,
search,
columnFilters,
setColumnFilters,
pagination,
setPagination,
pageCount,
onSetPageSize,
showSelectedRows = true,
showColumnToggle,
columnVisibility,
setColumnVisibility,
rowSelection,
setRowSelection,
isLoading,
getRowId,
emptyState,
card,
manualSorting = true,
manualFiltering = true,
}: DataTableProps<TData, TValue> & ExtraDataTableProps) {
const loadingNoData = isLoading && !data.length;
const tableData = React.useMemo(
() => (loadingNoData ? Array(10).fill({ metadata: {} }) : data),
[loadingNoData, data],
);
const tableColumns = React.useMemo(
() =>
loadingNoData
? columns.map((column) => ({
...column,
cell: () => <Skeleton className="h-4 w-[100px]" />,
}))
: columns,
[loadingNoData, columns],
);
const table = useReactTable({
data: tableData,
columns: tableColumns,
state: {
sorting,
columnVisibility,
rowSelection: rowSelection || {},
columnFilters,
pagination,
},
pageCount,
enableRowSelection: !!rowSelection,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
manualSorting,
manualFiltering,
manualPagination: true,
getRowId,
});
const getTableRow = (row: Row<TData>) => {
if (row.original.getRow) {
return row.original.getRow();
}
return (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className={cn(
row.original.isExpandable && 'cursor-pointer hover:bg-muted',
)}
onClick={row.original.onClick}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
);
};
const getTable = () => (
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{error ? (
<TableRow className="p-4 text-center text-red-500">
<TableCell colSpan={columns.length}>
{error.message || 'An error occurred.'}
</TableCell>
</TableRow>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => getTableRow(row))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
{emptyState || 'No results.'}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
const getCards = () => (
<div
className={
card?.containerStyle ||
'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3'
}
>
{error
? error.message || 'An error occurred.'
: table.getRowModel().rows?.length
? table
.getRowModel()
.rows.map((row) =>
card?.component
? card?.component({ data: row.original })
: null,
)
: emptyState || 'No results.'}
</div>
);
return (
<div className="space-y-4">
{(setSearch || actions || (filters && filters.length > 0)) && (
<DataTableToolbar
table={table}
filters={filters}
isLoading={isLoading}
actions={actions}
search={search}
setSearch={setSearch}
showColumnToggle={showColumnToggle}
/>
)}
<div className={`rounded-md ${!card && 'border'}`}>
{!card ? getTable() : getCards()}
</div>
{pagination && (
<DataTablePagination
table={table}
onSetPageSize={onSetPageSize}
showSelectedRows={showSelectedRows}
/>
)}
</div>
);
}
@@ -1,51 +0,0 @@
import React from 'react';
import { Button } from '@/components/ui/button';
export interface BannerProps {
message: JSX.Element;
type?: 'info' | 'warning' | 'success' | 'error';
actionText?: string;
onAction?: () => void;
}
export const Banner: React.FC<BannerProps> = ({
message,
type = 'info',
actionText,
onAction,
}) => {
const getBgColor = () => {
switch (type) {
case 'warning':
return 'bg-amber-50 dark:bg-amber-900/20 text-amber-800 dark:text-amber-200';
case 'success':
return 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200';
case 'error':
return 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200';
default:
return 'bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-200';
}
};
return (
<div className={`w-full py-2 px-4 h-12 ${getBgColor()}`}>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center">
<p className="text-sm font-medium">{message}</p>
</div>
<div className="flex items-center gap-2">
{actionText && onAction && (
<Button
variant="outline"
size="sm"
onClick={onAction}
className="text-xs font-medium hover:bg-opacity-20"
>
{actionText}
</Button>
)}
</div>
</div>
</div>
);
};
@@ -1,5 +1,5 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/v1/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
@@ -8,10 +8,10 @@ import {
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
} from '@/components/v1/ui/dropdown-menu';
import { useLocation, useNavigate } from 'react-router-dom';
import api, { TenantMember, TenantVersion, User } from '@/lib/api';
import { useNavigate } from 'react-router-dom';
import api, { TenantMember, User } from '@/lib/api';
import { useApiError } from '@/lib/hooks';
import { useMutation } from '@tanstack/react-query';
import hatchet from '@/assets/hatchet_logo.png';
@@ -29,12 +29,9 @@ import {
} from 'react-icons/bi';
import { Menu } from 'lucide-react';
import { useTheme } from '@/components/theme-provider';
import { useEffect, useMemo } from 'react';
import { useMemo } from 'react';
import useApiMeta from '@/pages/auth/hooks/use-api-meta';
import { VersionInfo } from '@/pages/main/info/components/version-info';
import { useTenant } from '@/lib/atoms';
import { routes } from '@/router';
import { Banner, BannerProps } from './banner';
import {
Breadcrumb,
BreadcrumbItem,
@@ -45,11 +42,12 @@ import {
} from '@/components/v1/ui/breadcrumb';
import { useBreadcrumbs } from '@/hooks/use-breadcrumbs';
import { usePendingInvites } from '@/hooks/use-pending-invites';
import { useTenantDetails } from '@/hooks/use-tenant';
function HelpDropdown() {
const meta = useApiMeta();
const navigate = useNavigate();
const { tenant } = useTenant();
const { tenant } = useTenantDetails();
const hasPylon = useMemo(() => {
if (!meta.data?.pylonAppId) {
@@ -101,11 +99,11 @@ function HelpDropdown() {
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
if (tenant?.version === TenantVersion.V1) {
navigate(`/tenants/${tenant.metadata.id}/onboarding/get-started`);
} else {
navigate('/onboarding/get-started');
if (!tenant) {
return;
}
navigate(`/tenants/${tenant.metadata.id}/onboarding/get-started`);
}}
>
<BiSolidGraduation className="mr-2" />
@@ -118,7 +116,6 @@ function HelpDropdown() {
function AccountDropdown({ user }: { user: User }) {
const navigate = useNavigate();
const { tenant } = useTenant();
const { handleApiError } = useApiError({});
@@ -176,14 +173,6 @@ function AccountDropdown({ user }: { user: User }) {
<DropdownMenuItem>
<VersionInfo />
</DropdownMenuItem>
{tenant?.version == TenantVersion.V1 &&
location.pathname.includes(`/tenants/${tenant.metadata.id}`) && (
<DropdownMenuItem
onClick={() => navigate('/workflow-runs?previewV0=true')}
>
View Legacy V0 Data
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => toggleTheme()}>
Toggle Theme
@@ -200,76 +189,15 @@ function AccountDropdown({ user }: { user: User }) {
interface MainNavProps {
user: User;
tenantMemberships: TenantMember[];
setHasBanner?: (state: boolean) => void;
}
export default function MainNav({ user, setHasBanner }: MainNavProps) {
export default function MainNav({ user }: MainNavProps) {
const { toggleSidebarOpen } = useSidebar();
const { theme } = useTheme();
const { tenant } = useTenant();
const { pathname } = useLocation();
const navigate = useNavigate();
const breadcrumbs = useBreadcrumbs();
const tenantedRoutes = useMemo(
() =>
routes
.at(0)
?.children?.find((r) => r.path?.startsWith('/tenants/'))
?.children?.find(
(r) => r.path?.startsWith('/tenants/') && r.children?.length,
)
?.children?.map((c) => c.path)
?.map((p) => p?.replace('/tenants/:tenant', '')) || [],
[],
);
const tenantVersion = tenant?.version || TenantVersion.V0;
const banner: BannerProps | undefined = useMemo(() => {
const pathnameWithoutTenant = pathname.replace(
`/tenants/${tenant?.metadata.id}`,
'',
);
const shouldShowVersionUpgradeButton =
tenantedRoutes.includes(pathnameWithoutTenant) && // It is a versioned route
!pathname.startsWith('/tenants') && // The user is not already on the v1 version
tenantVersion === TenantVersion.V1; // The tenant is on the v1 version
if (shouldShowVersionUpgradeButton) {
return {
message: (
<>
You are viewing legacy V0 data for a tenant that was upgraded to V1
runtime.
</>
),
type: 'warning',
actionText: 'View V1',
onAction: () => {
navigate({
pathname: `/tenants/${tenant?.metadata.id}${pathname}`,
search: '?previewV0=false',
});
},
};
}
return;
}, [navigate, pathname, tenantVersion, tenantedRoutes, tenant?.metadata.id]);
useEffect(() => {
if (!setHasBanner) {
return;
}
setHasBanner(!!banner);
}, [setHasBanner, banner]);
return (
<div className="fixed top-0 w-screen z-50">
{banner && <Banner {...banner} />}
<div className="h-16 border-b bg-background">
<div className="flex h-16 items-center pr-4 pl-4">
<div className="flex flex-row items-center gap-x-8">
@@ -1,114 +0,0 @@
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import {
BuildingOffice2Icon,
// ChartBarSquareIcon,
CheckIcon,
} from '@heroicons/react/24/outline';
import invariant from 'tiny-invariant';
import {
Command,
CommandEmpty,
CommandItem,
CommandList,
CommandSeparator,
} from '@/components/ui/command';
import { Link } from 'react-router-dom';
import { Tenant, TenantMember, TenantVersion } from '@/lib/api';
import { CaretSortIcon, PlusCircledIcon } from '@radix-ui/react-icons';
import {
PopoverTrigger,
Popover,
PopoverContent,
} from '@radix-ui/react-popover';
import React from 'react';
import { useTenant } from '@/lib/atoms';
import { Spinner } from '@/components/ui/loading.tsx';
import useApiMeta from '@/pages/auth/hooks/use-api-meta';
interface TenantSwitcherProps {
className?: string;
memberships: TenantMember[];
currTenant: Tenant;
}
export function TenantSwitcher({
className,
memberships,
currTenant,
}: TenantSwitcherProps) {
const meta = useApiMeta();
const { setTenant: setCurrTenant } = useTenant();
const [open, setOpen] = React.useState(false);
if (!currTenant) {
return <Spinner />;
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
aria-label="Select a team"
className={cn('w-full justify-between', className)}
>
<BuildingOffice2Icon className="mr-2 h-4 w-4" />
{currTenant.name}
<CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent side="right" className="w-full p-0 mb-6 z-50">
<Command className="min-w-[260px]" value={currTenant.slug}>
<CommandList>
<CommandEmpty>No tenants found.</CommandEmpty>
{memberships.map((membership) => (
<CommandItem
key={membership.metadata.id}
onSelect={() => {
invariant(membership.tenant);
setCurrTenant(membership.tenant);
setOpen(false);
if (membership.tenant.version === TenantVersion.V1) {
// Hack to wait for next event loop tick so local storage is updated
setTimeout(() => {
window.location.href = `/tenants/${membership.tenant?.metadata.id}`;
}, 0);
}
}}
value={membership.tenant?.slug}
className="text-sm cursor-pointer"
>
<BuildingOffice2Icon className="mr-2 h-4 w-4" />
{membership.tenant?.name}
<CheckIcon
className={cn(
'ml-auto h-4 w-4',
currTenant.slug === membership.tenant?.slug
? 'opacity-100'
: 'opacity-0',
)}
/>
</CommandItem>
))}
</CommandList>
{meta.data?.allowCreateTenant && (
<>
<CommandSeparator />
<CommandList>
<Link to="/onboarding/create-tenant">
<CommandItem className="text-sm cursor-pointer">
<PlusCircledIcon className="mr-2 h-4 w-4" />
New Tenant
</CommandItem>
</Link>
</CommandList>
</>
)}
</Command>
</PopoverContent>
</Popover>
);
}
@@ -1,94 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import TimeAgo from 'timeago-react';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
interface RelativeDateProps {
date?: Date | string;
future?: boolean;
}
const RelativeDate: React.FC<RelativeDateProps> = ({
date = '',
future = false,
}) => {
const formattedDate = useMemo(
() => (typeof date === 'string' ? new Date(date) : date),
[date],
);
const [countdown, setCountdown] = useState('');
useEffect(() => {
if (future) {
const updateCountdown = () => {
const currentDate = new Date();
const timeDiff = formattedDate.getTime() - currentDate.getTime();
if (timeDiff <= 0) {
setCountdown('');
return;
}
const days = Math.floor(timeDiff / (1000 * 3600 * 24));
const hours = Math.floor(
(timeDiff % (1000 * 3600 * 24)) / (1000 * 3600),
);
const minutes = Math.floor((timeDiff % (1000 * 3600)) / (1000 * 60));
const seconds = Math.floor((timeDiff % (1000 * 60)) / 1000);
const countdownParts = [];
if (days > 0) {
countdownParts.push(`${days}d`);
}
if (hours > 0 || days > 0) {
countdownParts.push(`${hours}h`);
}
if (minutes > 0 || hours > 0 || days > 0) {
countdownParts.push(`${minutes}m`);
}
countdownParts.push(`${seconds}s`);
setCountdown(countdownParts.join(' '));
};
updateCountdown();
const countdownInterval = setInterval(updateCountdown, 1000);
return () => {
clearInterval(countdownInterval);
};
}
}, [formattedDate, future]);
if (date == '0001-01-01T00:00:00Z') {
return null;
}
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger
onFocusCapture={(e) => {
e.stopPropagation();
}}
>
{future && countdown ? (
<>{countdown}</>
) : (
<TimeAgo datetime={formattedDate} />
)}
</TooltipTrigger>
<TooltipContent className="z-[80]">
{formattedDate.toLocaleString()}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
export default RelativeDate;
@@ -1,5 +1,5 @@
import { useTenantDetails } from '@/hooks/use-tenant';
import { User } from '@/lib/api';
import { useTenant } from '@/lib/atoms';
import useApiMeta from '@/pages/auth/hooks/use-api-meta';
import React, { PropsWithChildren, useEffect, useMemo } from 'react';
@@ -13,7 +13,7 @@ const SupportChat: React.FC<PropsWithChildren & SupportChatProps> = ({
}) => {
const meta = useApiMeta();
const { tenant } = useTenant();
const { tenant } = useTenantDetails();
const APP_ID = useMemo(() => {
if (!meta.data?.pylonAppId) {
@@ -1,76 +0,0 @@
import { add, format } from 'date-fns';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { TimePicker } from './time-picker';
import { CalendarIcon } from '@radix-ui/react-icons';
type DateTimePickerProps = {
date: Date | undefined;
setDate: (date: Date | undefined) => void;
label?: string;
};
const formatDateWithLabel = (date: Date, label?: string) => {
const formattedDate =
format(date, 'PPP HH:mm') + ' (' + format(date, 'z') + ')';
if (!label) {
return formattedDate;
}
return label + ': ' + formattedDate;
};
export function DateTimePicker({ date, setDate, label }: DateTimePickerProps) {
/**
* carry over the current time when a user clicks a new day
* instead of resetting to 00:00
*/
const handleSelect = (newDay: Date | undefined) => {
if (!newDay) {
return;
}
if (!date) {
setDate(newDay);
return;
}
const diff = newDay.getTime() - date.getTime();
const diffInDays = diff / (1000 * 60 * 60 * 24);
const newDateFull = add(date, { days: Math.ceil(diffInDays) });
setDate(newDateFull);
};
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={'outline'}
className={cn(
'w-fit justify-start text-left font-normal text-xs',
!date && 'text-muted-foreground',
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? formatDateWithLabel(date, label) : <span>{label}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={(d) => handleSelect(d)}
initialFocus
/>
<div className="p-3 border-t border-border">
<TimePicker setDate={setDate} date={date} />
</div>
</PopoverContent>
</Popover>
);
}
@@ -1,149 +0,0 @@
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import React from 'react';
import {
Period,
TimePickerType,
getArrowByType,
getDateByType,
setDateByType,
} from './time-picker-utils';
export interface TimePickerInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
picker: TimePickerType;
date: Date | undefined;
setDate: (date: Date | undefined) => void;
period?: Period;
onRightFocus?: () => void;
onLeftFocus?: () => void;
}
const TimePickerInput = React.forwardRef<
HTMLInputElement,
TimePickerInputProps
>(
(
{
className,
value,
id,
name,
date = new Date(new Date().setHours(0, 0, 0, 0)),
setDate,
onChange,
onKeyDown,
picker,
period,
onLeftFocus,
onRightFocus,
...props
},
ref,
) => {
const [flag, setFlag] = React.useState<boolean>(false);
const [prevIntKey, setPrevIntKey] = React.useState<string>('0');
/**
* allow the user to enter the second digit within 2 seconds
* otherwise start again with entering first digit
*/
React.useEffect(() => {
if (flag) {
const timer = setTimeout(() => {
setFlag(false);
}, 2000);
return () => clearTimeout(timer);
}
}, [flag]);
const calculatedValue = React.useMemo(() => {
return getDateByType(date, picker);
}, [date, picker]);
const calculateNewValue = (key: string) => {
/*
* If picker is '12hours' and the first digit is 0, then the second digit is automatically set to 1.
* The second entered digit will break the condition and the value will be set to 10-12.
*/
if (picker === '12hours') {
if (flag && calculatedValue.slice(1, 2) === '1' && prevIntKey === '0') {
return '0' + key;
}
}
return !flag ? '0' + key : calculatedValue.slice(1, 2) + key;
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Tab') {
return;
}
e.preventDefault();
if (e.key === 'ArrowRight') {
onRightFocus?.();
}
if (e.key === 'ArrowLeft') {
onLeftFocus?.();
}
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
const step = e.key === 'ArrowUp' ? 1 : -1;
const newValue = getArrowByType(calculatedValue, step, picker);
if (flag) {
setFlag(false);
}
const tempDate = new Date(date);
setDate(setDateByType(tempDate, newValue, picker, period));
}
if (e.key >= '0' && e.key <= '9') {
if (picker === '12hours') {
setPrevIntKey(e.key);
}
const newValue = calculateNewValue(e.key);
if (flag) {
onRightFocus?.();
}
setFlag((prev) => !prev);
const tempDate = new Date(date);
setDate(setDateByType(tempDate, newValue, picker, period));
}
};
const valueToTwoDigits = (value: number) => {
return value < 10 ? `0${value}` : value;
};
return (
<Input
ref={ref}
id={id || picker}
name={name || picker}
className={cn(
'w-[48px] text-xs text-center font-mono tabular-nums caret-transparent focus:bg-accent focus:text-accent-foreground [&::-webkit-inner-spin-button]:appearance-none',
className,
)}
value={value || valueToTwoDigits(parseInt(calculatedValue))}
onChange={(e) => {
e.preventDefault();
onChange?.(e);
}}
min={0}
max={59}
type="number"
inputMode="numeric"
onKeyDown={(e) => {
onKeyDown?.(e);
handleKeyDown(e);
}}
{...props}
/>
);
},
);
TimePickerInput.displayName = 'TimePickerInput';
export { TimePickerInput };
@@ -1,226 +0,0 @@
/**
* regular expression to check for valid hour format (01-23)
*/
export function isValidHour(value: string) {
return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
}
/**
* regular expression to check for valid 12 hour format (01-12)
*/
export function isValid12Hour(value: string) {
return /^(0[1-9]|1[0-2])$/.test(value);
}
/**
* regular expression to check for valid minute format (00-59)
*/
export function isValidMinuteOrSecond(value: string) {
return /^[0-5][0-9]$/.test(value);
}
type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };
export function getValidNumber(
value: string,
{ max, min = 0, loop = false }: GetValidNumberConfig,
) {
let numericValue = parseInt(value, 10);
if (!isNaN(numericValue)) {
if (!loop) {
if (numericValue > max) {
numericValue = max;
}
if (numericValue < min) {
numericValue = min;
}
} else {
if (numericValue > max) {
numericValue = min;
}
if (numericValue < min) {
numericValue = max;
}
}
return numericValue.toString().padStart(2, '0');
}
return '00';
}
export function getValidHour(value: string) {
if (isValidHour(value)) {
return value;
}
return getValidNumber(value, { max: 23 });
}
export function getValid12Hour(value: string) {
if (isValid12Hour(value)) {
return value;
}
return getValidNumber(value, { min: 1, max: 12 });
}
export function getValidMinuteOrSecond(value: string) {
if (isValidMinuteOrSecond(value)) {
return value;
}
return getValidNumber(value, { max: 59 });
}
type GetValidArrowNumberConfig = {
min: number;
max: number;
step: number;
};
export function getValidArrowNumber(
value: string,
{ min, max, step }: GetValidArrowNumberConfig,
) {
let numericValue = parseInt(value, 10);
if (!isNaN(numericValue)) {
numericValue += step;
return getValidNumber(String(numericValue), { min, max, loop: true });
}
return '00';
}
export function getValidArrowHour(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 23, step });
}
export function getValidArrow12Hour(value: string, step: number) {
return getValidArrowNumber(value, { min: 1, max: 12, step });
}
export function getValidArrowMinuteOrSecond(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 59, step });
}
export function setMinutes(date: Date, value: string) {
const minutes = getValidMinuteOrSecond(value);
date.setMinutes(parseInt(minutes, 10));
return date;
}
export function setSeconds(date: Date, value: string) {
const seconds = getValidMinuteOrSecond(value);
date.setSeconds(parseInt(seconds, 10));
return date;
}
export function setHours(date: Date, value: string) {
const hours = getValidHour(value);
date.setHours(parseInt(hours, 10));
return date;
}
export function set12Hours(date: Date, value: string, period: Period) {
const hours = parseInt(getValid12Hour(value), 10);
const convertedHours = convert12HourTo24Hour(hours, period);
date.setHours(convertedHours);
return date;
}
export type TimePickerType = 'minutes' | 'seconds' | 'hours' | '12hours';
export type Period = 'AM' | 'PM';
export function setDateByType(
date: Date,
value: string,
type: TimePickerType,
period?: Period,
) {
switch (type) {
case 'minutes':
return setMinutes(date, value);
case 'seconds':
return setSeconds(date, value);
case 'hours':
return setHours(date, value);
case '12hours': {
if (!period) {
return date;
}
return set12Hours(date, value, period);
}
default:
return date;
}
}
export function getDateByType(date: Date, type: TimePickerType) {
switch (type) {
case 'minutes':
return getValidMinuteOrSecond(String(date.getMinutes()));
case 'seconds':
return getValidMinuteOrSecond(String(date.getSeconds()));
case 'hours':
return getValidHour(String(date.getHours()));
case '12hours':
return getValid12Hour(String(display12HourValue(date.getHours())));
default:
return '00';
}
}
export function getArrowByType(
value: string,
step: number,
type: TimePickerType,
) {
switch (type) {
case 'minutes':
return getValidArrowMinuteOrSecond(value, step);
case 'seconds':
return getValidArrowMinuteOrSecond(value, step);
case 'hours':
return getValidArrowHour(value, step);
case '12hours':
return getValidArrow12Hour(value, step);
default:
return '00';
}
}
/**
* handles value change of 12-hour input
* 12:00 PM is 12:00
* 12:00 AM is 00:00
*/
export function convert12HourTo24Hour(hour: number, period: Period) {
if (period === 'PM') {
if (hour <= 11) {
return hour + 12;
} else {
return hour;
}
} else if (period === 'AM') {
if (hour === 12) {
return 0;
}
return hour;
}
return hour;
}
/**
* time is stored in the 24-hour form,
* but needs to be displayed to the user
* in its 12-hour representation
*/
export function display12HourValue(hours: number) {
if (hours === 0 || hours === 12) {
return '12';
}
if (hours >= 22) {
return `${hours - 12}`;
}
if (hours % 12 > 9) {
return `${hours}`;
}
return `0${hours % 12}`;
}
@@ -1,56 +0,0 @@
import * as React from 'react';
import { Label } from '@/components/ui/label';
import { TimePickerInput } from './time-picker-input';
interface TimePickerProps {
date: Date | undefined;
setDate: (date: Date | undefined) => void;
}
export function TimePicker({ date, setDate }: TimePickerProps) {
const minuteRef = React.useRef<HTMLInputElement>(null);
const hourRef = React.useRef<HTMLInputElement>(null);
const secondRef = React.useRef<HTMLInputElement>(null);
return (
<div className="flex items-end gap-2">
<div className="grid gap-1 text-center">
<Label htmlFor="hours" className="text-xs">
Hours
</Label>
<TimePickerInput
picker="hours"
date={date}
setDate={setDate}
ref={hourRef}
onRightFocus={() => minuteRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="minutes" className="text-xs">
Minutes
</Label>
<TimePickerInput
picker="minutes"
date={date}
setDate={setDate}
ref={minuteRef}
onLeftFocus={() => hourRef.current?.focus()}
onRightFocus={() => secondRef.current?.focus()}
/>
</div>
<div className="grid gap-1 text-center">
<Label htmlFor="seconds" className="text-xs">
Seconds
</Label>
<TimePickerInput
picker="seconds"
date={date}
setDate={setDate}
ref={secondRef}
onLeftFocus={() => minuteRef.current?.focus()}
/>
</div>
</div>
);
}
@@ -1,59 +0,0 @@
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils';
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn('border-b', className)}
{...props}
/>
));
AccordionItem.displayName = 'AccordionItem';
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
hideChevron?: boolean;
}
>(({ className, children, hideChevron, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
className,
)}
{...props}
>
{children}
{!hideChevron && (
<ChevronDownIcon className="h-4 w-4 shrink-0 text-gray-700 dark:text-gray-300 transition-transform duration-200" />
)}
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn('pb-4 pt-0', className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
-60
View File
@@ -1,60 +0,0 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
warn: 'border-yellow-400 dark:border-yellow-800',
},
},
defaultVariants: {
variant: 'default',
},
},
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
));
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
));
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };
-48
View File
@@ -1,48 +0,0 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '@/lib/utils';
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className,
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };
-42
View File
@@ -1,42 +0,0 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-md 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',
{
variants: {
variant: {
default: 'border-transparent bg-primary text-primary-foreground shadow',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground',
outlineDestructive:
'border border-destructive rounded-sm px-1 font-normal text-red-800 dark:text-red-300 bg-transparent',
successful:
'border-transparent rounded-sm px-1 font-normal text-green-800 dark:text-green-300 bg-green-500/20 ring-green-500/30',
failed:
'border-transparent rounded-sm px-1 font-normal text-red-800 dark:text-red-300 bg-red-500/20 ring-red-500/30',
inProgress:
'border-transparent rounded-sm px-1 font-normal text-yellow-800 dark:text-yellow-300 bg-yellow-500/20 ring-yellow-500/30',
},
},
defaultVariants: {
variant: 'default',
},
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<span className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };
@@ -1,115 +0,0 @@
import * as React from 'react';
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons';
import { Slot } from '@radix-ui/react-slot';
import { cn } from '@/lib/utils';
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<'nav'> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = 'Breadcrumb';
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<'ol'>
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
className,
)}
{...props}
/>
));
BreadcrumbList.displayName = 'BreadcrumbList';
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<'li'>
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
));
BreadcrumbItem.displayName = 'BreadcrumbItem';
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<'a'> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : 'a';
return (
<Comp
ref={ref}
className={cn('transition-colors hover:text-foreground', className)}
{...props}
/>
);
});
BreadcrumbLink.displayName = 'BreadcrumbLink';
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<'span'>
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn('font-normal text-foreground', className)}
{...props}
/>
));
BreadcrumbPage.displayName = 'BreadcrumbPage';
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<'li'>) => (
<li
role="presentation"
aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)}
{...props}
>
{children ?? <ChevronRightIcon />}
</li>
);
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<'span'>) => (
<span
role="presentation"
aria-hidden="true"
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<DotsHorizontalIcon className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
-58
View File
@@ -1,58 +0,0 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
xs: '',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = 'Button';
export { Button, buttonVariants };
@@ -1,70 +0,0 @@
import * as React from 'react';
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons';
import { DayPicker } from 'react-day-picker';
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell:
'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: cn(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
props.mode === 'range'
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
: '[&:has([aria-selected])]:rounded-md',
),
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
),
day_range_start: 'day-range-start',
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle:
'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: () => <ChevronLeftIcon className="h-4 w-4" />,
IconRight: () => <ChevronRightIcon className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };
-83
View File
@@ -1,83 +0,0 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-xl border bg-card text-card-foreground shadow',
className,
)}
{...props}
/>
));
Card.displayName = 'Card';
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
));
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
));
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};
-363
View File
@@ -1,363 +0,0 @@
import * as React from 'react';
import * as RechartsPrimitive from 'recharts';
import { cn } from '@/lib/utils';
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: '', dark: '.dark' } as const;
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode;
icon?: React.ComponentType;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
);
};
type ChartContextProps = {
config: ChartConfig;
};
const ChartContext = React.createContext<ChartContextProps | null>(null);
function useChart() {
const context = React.useContext(ChartContext);
if (!context) {
throw new Error('useChart must be used within a <ChartContainer />');
}
return context;
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> & {
config: ChartConfig;
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>['children'];
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
);
});
ChartContainer.displayName = 'Chart';
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color,
);
if (!colorConfig.length) {
return null;
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color;
return color ? ` --color-${key}: ${color};` : null;
})
.join('\n')}
}
`,
)
.join('\n'),
}}
/>
);
};
const ChartTooltip = RechartsPrimitive.Tooltip;
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<'div'> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: 'line' | 'dot' | 'dashed';
nameKey?: string;
labelKey?: string;
}
>(
(
{
active,
payload,
className,
indicator = 'dot',
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref,
) => {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null;
}
const [item] = payload;
const key = `${labelKey || item.dataKey || item.name || 'value'}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const value =
!labelKey && typeof label === 'string'
? config[label as keyof typeof config]?.label || label
: itemConfig?.label;
if (labelFormatter) {
return (
<div className={cn('font-medium', labelClassName)}>
{labelFormatter(value, payload)}
</div>
);
}
if (!value) {
return null;
}
return <div className={cn('font-medium', labelClassName)}>{value}</div>;
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
]);
if (!active || !payload?.length) {
return null;
}
const nestLabel = payload.length === 1 && indicator !== 'dot';
return (
<div
ref={ref}
className={cn(
'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',
className,
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || 'value'}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const indicatorColor = color || item.payload.fill || item.color;
return (
<div
key={item.dataKey}
className={cn(
'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',
indicator === 'dot' && 'items-center',
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
'shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]',
{
'h-2.5 w-2.5': indicator === 'dot',
'w-1': indicator === 'line',
'w-0 border-[1.5px] border-dashed bg-transparent':
indicator === 'dashed',
'my-0.5': nestLabel && indicator === 'dashed',
},
)}
style={
{
'--color-bg': indicatorColor,
'--color-border': indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
'flex flex-1 justify-between leading-none',
nestLabel ? 'items-end' : 'items-center',
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
);
})}
</div>
</div>
);
},
);
ChartTooltipContent.displayName = 'ChartTooltip';
const ChartLegend = RechartsPrimitive.Legend;
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'> &
Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {
hideIcon?: boolean;
nameKey?: string;
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey },
ref,
) => {
const { config } = useChart();
if (!payload?.length) {
return null;
}
return (
<div
ref={ref}
className={cn(
'flex items-center justify-center gap-4',
verticalAlign === 'top' ? 'pb-3' : 'pt-3',
className,
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || 'value'}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
return (
<div
key={item.value}
className={cn(
'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground',
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
);
})}
</div>
);
},
);
ChartLegendContent.displayName = 'ChartLegend';
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string,
) {
if (typeof payload !== 'object' || payload === null) {
return undefined;
}
const payloadPayload =
'payload' in payload &&
typeof payload.payload === 'object' &&
payload.payload !== null
? payload.payload
: undefined;
let configLabelKey: string = key;
if (
key in payload &&
typeof payload[key as keyof typeof payload] === 'string'
) {
configLabelKey = payload[key as keyof typeof payload] as string;
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string;
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config];
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
};
@@ -1,28 +0,0 @@
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
import { cn } from '@/lib/utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };
@@ -1,219 +0,0 @@
import CopyToClipboard from './copy-to-clipboard';
import { cn } from '@/lib/utils';
import Editor, { DiffEditor, Monaco } from '@monaco-editor/react';
import 'monaco-themes/themes/Pastels on Dark.json';
import { useTheme } from '../theme-provider';
interface CodeEditorProps {
code: string;
setCode?: (code: string | undefined) => void;
language: string;
className?: string;
height?: string;
width?: string;
copy?: boolean;
wrapLines?: boolean;
lineNumbers?: boolean;
}
export function CodeEditor({
code,
setCode,
language,
className,
height,
width,
copy = true,
wrapLines = true,
lineNumbers = false,
}: CodeEditorProps) {
const { theme } = useTheme();
const setEditorTheme = (monaco: Monaco) => {
monaco.editor.defineTheme('pastels-on-dark', getMonacoTheme());
monaco.editor.setTheme('pastels-on-dark');
};
const editorTheme = theme === 'dark' ? 'pastels-on-dark' : '';
return (
<div
className={cn(
className,
'w-full h-fit relative rounded-lg overflow-hidden',
)}
>
<Editor
beforeMount={setEditorTheme}
language={language}
value={code}
onChange={setCode}
width={width || '100%'}
height={height || '400px'}
theme={editorTheme}
options={{
minimap: { enabled: false },
wordWrap: wrapLines ? 'on' : 'off',
lineNumbers: lineNumbers
? function (lineNumber) {
return `<span style="padding-right:8px">${lineNumber}</span>`;
}
: 'off',
theme: editorTheme,
autoDetectHighContrast: true,
readOnly: !setCode,
scrollbar: { vertical: 'hidden', horizontal: 'hidden' },
showFoldingControls: language == 'json' ? 'always' : 'never',
lineDecorationsWidth: 0,
overviewRulerBorder: false,
colorDecorators: false,
hideCursorInOverviewRuler: true,
contextmenu: false,
}}
/>
{copy && (
<CopyToClipboard
className="absolute top-2 right-2"
text={code.trim()}
/>
)}
</div>
);
}
export function DiffCodeEditor({
code,
setCode,
language,
className,
height,
width,
copy,
originalValue,
wrapLines = true,
}: CodeEditorProps & {
originalValue: string;
}) {
const setEditorTheme = (monaco: Monaco) => {
monaco.editor.defineTheme('pastels-on-dark', getMonacoTheme());
monaco.editor.setTheme('pastels-on-dark');
};
return (
<div
className={cn(
className,
'w-full h-fit relative rounded-lg overflow-hidden',
)}
>
<DiffEditor
beforeMount={setEditorTheme}
language={language}
width={width || '100%'}
height={height || '400px'}
theme="pastels-on-dark"
original={originalValue}
modified={code}
options={{
minimap: { enabled: false },
wordWrap: wrapLines ? 'on' : 'off',
lineNumbers: 'off',
readOnly: !setCode,
scrollbar: { vertical: 'hidden', horizontal: 'hidden' },
showFoldingControls: language == 'json' ? 'always' : 'never',
lineDecorationsWidth: 0,
overviewRulerBorder: false,
colorDecorators: false,
hideCursorInOverviewRuler: true,
contextmenu: false,
}}
/>
{copy && (
<CopyToClipboard
className="absolute top-2 right-2"
text={code.trim()}
/>
)}
</div>
);
}
type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black' | 'hc-light';
const getMonacoTheme = () => {
return {
base: 'vs-dark' as BuiltinTheme,
inherit: true,
rules: [
{
background: '0D0D0D',
token: '',
},
{
foreground: '473c45',
token: 'comment',
},
{
foreground: 'c0c5ce',
token: 'string',
},
{
foreground: 'a8885a',
token: 'constant',
},
{
foreground: '4FB4D7',
token: 'variable.parameter',
},
{
foreground: '596380',
token: 'variable.other',
},
{
foreground: '728059',
token: 'keyword - keyword.operator',
},
{
foreground: '728059',
token: 'keyword.operator.logical',
},
{
foreground: '9ebf60',
token: 'storage',
},
{
foreground: '6078bf',
token: 'entity',
},
{
fontStyle: 'italic',
token: 'entity.other.inherited-class',
},
{
foreground: '8a4b66',
token: 'support',
},
{
foreground: '893062',
token: 'support.type.exception',
},
{
background: '5f0047',
token: 'invalid',
},
{
background: '371d28',
token: 'meta.function.section',
},
],
colors: {
'editor.foreground': '#c0c5ce',
'editor.background': '#1e293b',
'editor.selectionBackground': '#40002F',
'editor.lineHighlightBackground': '#00000012',
'editorCursor.foreground': '#7F005D',
'editorWhitespace.foreground': '#BFBFBF',
},
};
};
@@ -1,77 +0,0 @@
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import typescript from 'react-syntax-highlighter/dist/esm/languages/hljs/typescript';
import yaml from 'react-syntax-highlighter/dist/esm/languages/hljs/yaml';
import json from 'react-syntax-highlighter/dist/esm/languages/hljs/json';
import {
anOldHope,
atomOneLight,
} from 'react-syntax-highlighter/dist/esm/styles/hljs';
import CopyToClipboard from './copy-to-clipboard';
import { cn } from '@/lib/utils';
import { useTheme } from '../theme-provider';
SyntaxHighlighter.registerLanguage('typescript', typescript);
SyntaxHighlighter.registerLanguage('yaml', yaml);
SyntaxHighlighter.registerLanguage('json', json);
export function CodeHighlighter({
code,
copyCode,
language,
className,
maxHeight,
minHeight,
maxWidth,
copy = true,
wrapLines = true,
}: {
code: string;
copyCode?: string;
language: string;
className?: string;
maxHeight?: string;
minHeight?: string;
maxWidth?: string;
copy?: boolean;
wrapLines?: boolean;
}) {
const { theme } = useTheme();
return (
<div className={cn('w-full h-fit relative bg-muted rounded-lg', className)}>
<SyntaxHighlighter
language={language}
style={theme == 'dark' ? anOldHope : atomOneLight}
wrapLines={wrapLines}
lineProps={{
style: { wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
}}
customStyle={{
cursor: 'default',
borderRadius: '0.5rem',
maxHeight: maxHeight,
minHeight: minHeight,
maxWidth: maxWidth,
fontFamily:
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
fontSize: '0.75rem',
lineHeight: '1rem',
padding: '0.5rem',
paddingRight: '2rem',
flex: '1',
background: 'transparent',
}}
>
{code.trim()}
</SyntaxHighlighter>
{copy && (
<CopyToClipboard
className="absolute top-2 right-2"
text={(copyCode || code).trim()}
/>
)}
</div>
);
}
@@ -1,9 +0,0 @@
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
-157
View File
@@ -1,157 +0,0 @@
import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Command as CommandPrimitive } from 'cmdk';
import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from '@/components/ui/dialog';
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className,
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => {
// noinspection HtmlUnknownAttribute
return (
// eslint-disable-next-line react/no-unknown-property
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
/>
</div>
);
});
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
className,
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('-mx-1 h-px bg-border', className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-gray-700 dark:text-gray-300',
className,
)}
{...props}
/>
);
};
CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};
@@ -1,51 +0,0 @@
import React, { useState } from 'react';
import { Button } from './button';
import { CopyIcon } from '@radix-ui/react-icons';
import { CheckIcon } from '@heroicons/react/24/outline';
import { cn } from '@/lib/utils';
type Props = {
text: string;
className?: string;
withText?: boolean;
onCopy?: () => void;
};
const CopyToClipboard: React.FC<Props> = ({
text,
className,
withText,
onCopy,
}) => {
const [successCopy, setSuccessCopy] = useState(false);
return (
<Button
className={cn(
className,
withText
? 'cursor-pointer flex flex-row gap-2 items-center mt-2'
: 'w-6 h-6 p-0 cursor-pointer',
)}
variant={withText ? 'default' : 'ghost'}
onClick={() => {
navigator.clipboard.writeText(text);
setSuccessCopy(true);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
onCopy && onCopy();
setTimeout(() => {
setSuccessCopy(false);
}, 2000);
}}
>
{successCopy ? (
<CheckIcon className="w-4 h-4" />
) : (
<CopyIcon className="w-4 h-4" />
)}
{withText && (successCopy ? 'Copied' : 'Copy to clipboard')}
</Button>
);
};
export default CopyToClipboard;
-119
View File
@@ -1,119 +0,0 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { cn } from '@/lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className,
)}
{...props}
>
{children}
{/* <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close> */}
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left',
className,
)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className,
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-gray-700 dark:text-gray-300', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
@@ -1,203 +0,0 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from '@radix-ui/react-icons';
import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className,
)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};
-221
View File
@@ -1,221 +0,0 @@
import React, { useEffect } from 'react';
import { Input } from '@/components/v1/ui/input';
import { Textarea } from './textarea';
import { Button } from '@/components/v1/ui/button';
import { cn } from '@/lib/utils';
import { TrashIcon } from '@radix-ui/react-icons';
export type KeyValueType = {
key: string;
value: string;
hidden: boolean;
locked: boolean;
deleted: boolean;
id?: string; // Optional ID for existing env vars
isEditing?: boolean; // Whether the value is being edited
hint?: string; // Hint value for existing secrets
};
type PropsType = {
label?: string;
values: KeyValueType[];
setValues?: (x: KeyValueType[]) => void;
disabled?: boolean;
fileUpload?: boolean;
secretOption?: boolean;
onDeleteIds?: (ids: string[]) => void; // Callback for deleted IDs
onAdd?: (values: KeyValueType[]) => void; // Callback for added values
onUpdate?: (values: KeyValueType[]) => void; // Callback for updated values
};
const EnvGroupArray: React.FC<PropsType> = ({
label,
values,
setValues = () => {},
disabled,
secretOption,
onDeleteIds,
onAdd,
onUpdate,
}) => {
useEffect(() => {
if (!values) {
setValues([]);
}
}, [setValues, values]);
const handleValueChange = (index: number, key: string, value: any) => {
const newValues = [...values];
const entry = newValues[index];
// If this is an existing secret and we're editing the value
if (key === 'value' && entry.hint) {
// Only set isEditing to true if the value is different from the hint
// and not empty (which would be the case when no changes are made)
if (value !== entry.hint && value !== '') {
newValues[index] = { ...entry, [key]: value, isEditing: true };
}
} else {
newValues[index] = { ...entry, [key]: value };
}
setValues(newValues);
// If this is an existing entry (has an ID), notify about updates
if (entry.id && onUpdate && value !== entry.hint) {
onUpdate([newValues[index]]);
}
};
const handleDeleteToggle = (index: number) => {
const newValues = [...values];
const entry = newValues[index];
const newDeletedState = !entry.deleted;
newValues[index] = { ...entry, deleted: newDeletedState };
setValues(newValues);
// If this is an existing env var with an ID, notify parent of deleted IDs
if (entry.id && onDeleteIds) {
const deletedIds = values
.filter((v) => v.id && v.deleted)
.map((v) => v.id as string);
onDeleteIds(deletedIds);
}
};
const handleRemove = (index: number) => {
const entry = values[index];
if (entry.id) {
// For existing entries, mark as deleted
const newValues = [...values];
newValues[index] = { ...entry, deleted: true };
setValues(newValues);
if (onDeleteIds) {
const deletedIds = values
.filter((v) => v.id && v.deleted)
.map((v) => v.id as string);
onDeleteIds(deletedIds);
}
} else {
// For new entries, remove completely
const newValues = values.filter((_, i) => i !== index);
setValues(newValues);
if (onAdd) {
onAdd(newValues.filter((v) => !v.id));
}
}
};
return (
<>
{label && <div className="mb-2 text-white">{label}</div>}
{values?.map((entry: KeyValueType, i: number) => (
<div
className={cn(
'mb-2 flex items-center gap-2',
entry.deleted && 'opacity-50 [&>*]:line-through',
)}
key={i}
>
<Input
placeholder="ex: key"
value={entry.key}
onChange={(e) => handleValueChange(i, 'key', e.target.value)}
disabled={disabled || entry.locked || entry.deleted}
className={cn(
'w-64',
entry.locked && 'bg-gray-200 cursor-not-allowed',
)}
/>
{entry.hidden ? (
<Input
placeholder={entry.hint}
value={entry.isEditing ? entry.value : undefined}
onChange={(e) => handleValueChange(i, 'value', e.target.value)}
type="password"
disabled={disabled || entry.locked || entry.deleted}
className={cn(
'flex-1',
entry.locked && 'bg-gray-200 cursor-not-allowed',
!entry.isEditing && entry.hint && 'text-gray-400',
)}
/>
) : (
<Textarea
placeholder={entry.hint}
value={entry.isEditing ? entry.value : undefined}
onChange={(e: any) =>
handleValueChange(i, 'value', e.target.value)
}
rows={entry.value?.split('\n').length || 0}
disabled={disabled || entry.locked || entry.deleted}
className={cn(
'flex-1',
entry.locked && 'bg-gray-200 cursor-not-allowed',
!entry.isEditing && entry.hint && 'text-gray-400',
)}
/>
)}
{secretOption && (
<Button
variant="ghost"
onClick={() =>
!entry.locked && handleValueChange(i, 'hidden', !entry.hidden)
}
disabled={entry.locked || entry.deleted}
>
{entry.hidden ? 'Unlock' : 'Lock'}
</Button>
)}
{!disabled && (
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
if (entry.id) {
handleDeleteToggle(i);
} else {
handleRemove(i);
}
}}
>
<TrashIcon className="h-4 w-4" />
</Button>
</div>
)}
</div>
))}
{!disabled && (
<div className="flex items-center">
<Button
variant="secondary"
onClick={() => {
const newEntry = {
key: '',
value: '',
hidden: false,
locked: false,
deleted: false,
};
const newValues = [...values, newEntry];
setValues(newValues);
if (onAdd) {
onAdd([...values.filter((v) => !v.id), newEntry]);
}
}}
>
Add row
</Button>
</div>
)}
</>
);
};
export default EnvGroupArray;
@@ -1,44 +0,0 @@
import { ObjectFieldTemplateProps } from '@rjsf/utils';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import { DEFAULT_COLLAPSED } from '../json-form';
export const CollapsibleSection = (props: ObjectFieldTemplateProps) => {
if (!props.title) {
return props.properties.map((element, i) => (
<div className="property-wrapper ml-4" key={i}>
{element.content}
</div>
));
}
return (
<Accordion
type="single"
collapsible
className="w-full"
defaultValue={DEFAULT_COLLAPSED.includes(props.title) ? 'closed' : 'open'}
>
<AccordionItem value="open">
<AccordionTrigger>{props.title}</AccordionTrigger>
<AccordionContent>
{props.description}
{props.properties?.length > 0 ? (
props.properties.map((element, i) => (
<div className="property-wrapper ml-4" key={i}>
{element.content}
</div>
))
) : (
<div className="ml-4">empty state</div>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
);
};
@@ -1,100 +0,0 @@
import { BaseInputTemplateProps, getInputProps } from '@rjsf/utils';
import {
ChangeEvent,
FocusEvent,
KeyboardEvent,
useContext,
useEffect,
useRef,
} from 'react';
import { JSONFormContext } from '../json-form';
export function DynamicSizeInputTemplate(props: BaseInputTemplateProps) {
const {
schema,
id,
options,
label,
value,
type,
placeholder,
required,
disabled,
readonly,
autofocus,
onChange,
onChangeOverride,
onBlur,
onFocus,
rawErrors,
hideError,
uiSchema,
registry,
formContext,
...rest
} = props;
const ref = useRef<HTMLTextAreaElement>(null);
const { form } = useContext(JSONFormContext);
const onTextChange = ({
target: { value: val },
}: ChangeEvent<HTMLTextAreaElement>) => {
// Use the options.emptyValue if it is specified and newVal is also an empty string
onChange(val === '' ? options.emptyValue || '' : val);
};
const onTextBlur = ({
target: { value: val },
}: FocusEvent<HTMLTextAreaElement>) => onBlur(id, val);
const onTextFocus = ({
target: { value: val },
}: FocusEvent<HTMLTextAreaElement>) => onFocus(id, val);
const inputProps = { ...rest, ...getInputProps(schema, type, options) };
delete inputProps.hideLabel; // hideLabel is not a valid prop for textarea
const setHeight = (e: HTMLTextAreaElement) => {
e.style.height = 'auto';
e.style.height = `${e.scrollHeight}px`;
};
useEffect(() => {
// Call adjustHeight whenever the watched value changes externally
if (!ref.current) {
return;
}
setHeight(ref.current);
}, [ref.current?.value]);
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (!form?.current) {
return;
}
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
form.current.submit();
}
};
return (
<textarea
ref={ref}
id={id}
value={value}
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
autoFocus={autofocus}
className="overflow-y-hidden"
onKeyDown={handleKeyDown}
onChange={
(onChangeOverride as
| ((event: ChangeEvent<HTMLTextAreaElement>) => void)
| undefined) || onTextChange
}
onBlur={onTextBlur}
onFocus={onTextFocus}
{...inputProps}
/>
);
}
@@ -1,27 +0,0 @@
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import { cn } from '@/lib/utils';
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };
-148
View File
@@ -1,148 +0,0 @@
type IconProps = React.HTMLAttributes<SVGElement>;
export const Icons = {
logo: (props: IconProps) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" {...props}>
<rect width="256" height="256" fill="none" />
<line
x1="208"
y1="128"
x2="128"
y2="208"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
/>
<line
x1="192"
y1="40"
x2="40"
y2="192"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16"
/>
</svg>
),
twitter: (props: IconProps) => (
<svg
{...props}
height="23"
viewBox="0 0 1200 1227"
width="23"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" />
</svg>
),
gitHub: (props: IconProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
),
radix: (props: IconProps) => (
<svg viewBox="0 0 25 25" fill="none" {...props}>
<path
d="M12 25C7.58173 25 4 21.4183 4 17C4 12.5817 7.58173 9 12 9V25Z"
fill="currentcolor"
></path>
<path d="M12 0H4V8H12V0Z" fill="currentcolor"></path>
<path
d="M17 8C19.2091 8 21 6.20914 21 4C21 1.79086 19.2091 0 17 0C14.7909 0 13 1.79086 13 4C13 6.20914 14.7909 8 17 8Z"
fill="currentcolor"
></path>
</svg>
),
aria: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M13.966 22.624l-1.69-4.281H8.122l3.892-9.144 5.662 13.425zM8.884 1.376H0v21.248zm15.116 0h-8.884L24 22.624Z" />
</svg>
),
npm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z"
fill="currentColor"
/>
</svg>
),
yarn: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z"
fill="currentColor"
/>
</svg>
),
pnpm: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z"
fill="currentColor"
/>
</svg>
),
react: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"
fill="currentColor"
/>
</svg>
),
tailwind: (props: IconProps) => (
<svg viewBox="0 0 24 24" {...props}>
<path
d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z"
fill="currentColor"
/>
</svg>
),
google: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
fill="currentColor"
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
/>
</svg>
),
apple: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
fill="currentColor"
/>
</svg>
),
paypal: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path
d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.565-5.946.36-1.847.174-3.388-.777-4.471z"
fill="currentColor"
/>
</svg>
),
spinner: (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
),
};
-25
View File
@@ -1,25 +0,0 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = 'Input';
export { Input };
@@ -1,159 +0,0 @@
import { cn } from '@/lib/utils';
import {
RJSFSchema,
RJSFValidationError,
UiSchema,
ValidationData,
ValidatorType,
} from '@rjsf/utils';
import Form from '@rjsf/core';
import { PlayIcon } from '@radix-ui/react-icons';
import { Button } from './button';
import { Loading } from './loading';
import { CollapsibleSection } from './form-inputs/collapsible-section';
import { DynamicSizeInputTemplate } from './form-inputs/dynamic-size-input-template';
import { createContext, useRef } from 'react';
type JSONPrimitive = string | number | boolean | null | Array<JSONPrimitive>;
export type JSONType = {
[key: string]: JSONType | JSONPrimitive | Array<JSONType>;
};
export const DEFAULT_COLLAPSED = ['advanced', 'user data'];
class NoValidation implements ValidatorType {
validateFormData(): ValidationData<any> {
return { errors: [], errorSchema: {} };
}
toErrorList(): RJSFValidationError[] {
return [];
}
isValid(): boolean {
return true;
}
rawValidation() {
return {};
}
}
interface JSONFormContextSchema {
form?: React.RefObject<Form>;
}
export const JSONFormContext = createContext<JSONFormContextSchema>({
form: undefined,
});
export function JsonForm({
inputSchema,
inputData,
className,
setInput,
disabled,
onSubmit,
}: {
inputSchema: JSONType;
className?: string;
inputData: JSONType;
setInput: React.Dispatch<React.SetStateAction<string>>;
disabled?: boolean;
onSubmit: () => void;
}) {
const formRef = useRef<Form>(null);
const schema = {
...inputSchema,
required: undefined,
$schema: undefined,
properties: {
...(inputSchema.properties as any),
triggered_by: undefined,
advanced: {
// Transform the schema to wrap the triggered by field
type: 'object',
properties: {
triggered_by: inputSchema.properties
? (inputSchema.properties as any).triggered_by
: undefined,
},
},
},
} as RJSFSchema;
delete schema.properties?.triggered_by;
const uiSchema: UiSchema<any, RJSFSchema, any> = {
input: {
'ui:title': 'workflow input',
},
parents: {
'ui:title': 'parent step data',
},
overrides: {
'ui:title': 'step overrides',
},
user_data: {
'ui:title': 'user data',
},
'ui:order': ['input', 'overrides', 'parents', '*'],
};
return (
<JSONFormContext.Provider value={{ form: formRef }}>
<div
className={cn(
className,
'w-full h-fit relative rounded-lg overflow-hidden',
)}
>
<Form
ref={formRef}
formData={inputData}
schema={schema}
disabled={disabled}
templates={{
BaseInputTemplate: DynamicSizeInputTemplate,
ObjectFieldTemplate: CollapsibleSection,
}}
uiSchema={uiSchema}
validator={new NoValidation()}
noHtml5Validate={true}
onChange={(data) => {
// Transform the data to unwrap the advanced fields
const formData = { ...data.formData, ...data.formData.advanced };
delete formData.advanced;
setInput((prev) =>
JSON.stringify({
...JSON.parse(prev),
...formData,
}),
);
}}
onSubmit={onSubmit}
onError={(e) => {
console.error(e);
}}
>
<Button className="w-fit invisible" disabled={disabled}>
{disabled ? (
<>
<Loading />
Playing
</>
) : (
<>
<PlayIcon
className={cn(disabled ? 'rotate-180' : '', 'h-4 w-4 mr-2')}
/>
Play Step
</>
)}
</Button>
</Form>
</div>
</JSONFormContext.Provider>
);
}
-24
View File
@@ -1,24 +0,0 @@
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };
@@ -1,14 +0,0 @@
import { Icons } from '@/components/ui/icons.tsx';
import { cn } from '@/lib/utils';
export function Spinner() {
return <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />;
}
export function Loading({ className }: { className?: string }) {
return (
<div className={cn('flex flex-row flex-1 w-full h-full', className)}>
<Spinner />
</div>
);
}
-238
View File
@@ -1,238 +0,0 @@
import * as React from 'react';
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from '@radix-ui/react-icons';
import * as MenubarPrimitive from '@radix-ui/react-menubar';
import { cn } from '@/lib/utils';
const MenubarMenu = MenubarPrimitive.Menu;
const MenubarGroup = MenubarPrimitive.Group;
const MenubarPortal = MenubarPrimitive.Portal;
const MenubarSub = MenubarPrimitive.Sub;
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
'flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm',
className,
)}
{...props}
/>
));
Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
className,
)}
{...props}
/>
));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props },
ref,
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</MenubarPrimitive.Portal>
),
);
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
{...props}
/>
));
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className,
)}
{...props}
/>
));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-gray-700 dark:text-gray-300',
className,
)}
{...props}
/>
);
};
MenubarShortcut.displayname = 'MenubarShortcut';
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
};
@@ -1,37 +0,0 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
container?: HTMLElement | null;
}
>(
(
{ className, container, align = 'center', sideOffset = 4, ...props },
ref,
) => (
<PopoverPrimitive.Portal container={container}>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 rounded-lg border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
),
);
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
@@ -1,46 +0,0 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };
@@ -1,191 +0,0 @@
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import typescript from 'react-syntax-highlighter/dist/esm/languages/hljs/typescript';
import yaml from 'react-syntax-highlighter/dist/esm/languages/hljs/yaml';
import json from 'react-syntax-highlighter/dist/esm/languages/hljs/json';
import {
anOldHope,
atomOneLight,
} from 'react-syntax-highlighter/dist/esm/styles/hljs';
import CopyToClipboard from './copy-to-clipboard';
import { useRef, useState } from 'react';
import { cn } from '@/lib/utils';
import { useTheme } from '../theme-provider';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Button } from './button';
import { CaretSortIcon } from '@radix-ui/react-icons';
SyntaxHighlighter.registerLanguage('typescript', typescript);
SyntaxHighlighter.registerLanguage('yaml', yaml);
SyntaxHighlighter.registerLanguage('json', json);
type Secrets = Record<string, string>;
enum Formats {
TABLE = 'table',
JSON = 'json',
YAML = 'yaml',
DOTENV = 'dotenv',
CLI = 'cli',
}
export function SecretCopier({
secrets,
className,
maxHeight,
maxWidth,
copy,
onClick,
}: {
secrets: Secrets;
className?: string;
maxHeight?: string;
maxWidth?: string;
copy?: boolean;
onClick?: () => void;
}) {
const { theme } = useTheme();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [format, setFormat] = useState<Formats>(Formats.DOTENV);
const renderSecrets = () => {
switch (format) {
case Formats.JSON:
return JSON.stringify(secrets, null, 2);
case Formats.YAML:
return toYAML(secrets);
case Formats.TABLE:
return (
<table className="w-full">
<thead>
<tr>
<th>Env Var</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{Object.entries(secrets).map(([key, value]) => (
<tr key={key}>
<td>
<CopyToClipboard text={key} /> {key}
</td>
<td>
<CopyToClipboard text={value} /> {value}
</td>
</tr>
))}
</tbody>
</table>
);
case Formats.CLI:
return toCliEnv(secrets);
case Formats.DOTENV:
default:
return toDotEnv(secrets);
}
};
return (
<div className={cn(className, 'w-full h-fit relative')}>
<div className="mb-2 justify-right flex flex-row items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="-ml-3 h-8 data-[state=open]:bg-accent"
>
<span>{format}</span>
<CaretSortIcon className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => setFormat(Formats.DOTENV)}>
{Formats.DOTENV}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFormat(Formats.CLI)}>
{Formats.CLI}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFormat(Formats.JSON)}>
{Formats.JSON}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFormat(Formats.YAML)}>
{Formats.YAML}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFormat(Formats.TABLE)}>
{Formats.TABLE}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div
role="button"
tabIndex={0}
onKeyDown={() => textareaRef.current?.focus()}
onClick={() => {
textareaRef.current?.focus();
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
onClick && onClick();
}}
className="relative flex bg-muted rounded-lg"
>
{format === Formats.TABLE ? (
renderSecrets()
) : (
<SyntaxHighlighter
language="text"
style={theme === 'dark' ? anOldHope : atomOneLight}
wrapLines={false}
lineProps={{
style: { wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
}}
customStyle={{
cursor: 'default',
borderRadius: '0.5rem',
maxHeight: maxHeight,
maxWidth: maxWidth,
fontFamily:
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
fontSize: '0.75rem',
lineHeight: '1rem',
padding: '0.5rem',
flex: '1',
background: 'transparent',
}}
>
{renderSecrets() as string}
</SyntaxHighlighter>
)}
</div>
{copy && format !== Formats.TABLE && (
<CopyToClipboard
text={renderSecrets() as string}
withText
onCopy={() => onClick && onClick()}
/>
)}
</div>
);
}
function toDotEnv(s: Secrets) {
return Object.entries(s)
.map(([key, value]) => `${key}="${value}"`)
.join('\n');
}
function toCliEnv(s: Secrets) {
return Object.entries(s)
.map(([key, value]) => `export ${key}="${value}"`)
.join('\n');
}
function toYAML(s: Secrets) {
return Object.entries(s)
.map(([key, value]) => `${key}:"${value}"`)
.join('\n');
}
-162
View File
@@ -1,162 +0,0 @@
import * as React from 'react';
import {
CaretSortIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from '@radix-ui/react-icons';
import * as SelectPrimitive from '@radix-ui/react-select';
import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};
@@ -1,29 +0,0 @@
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '@/lib/utils';
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = 'horizontal', decorative = true, ...props },
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}
{...props}
/>
),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator };
-138
View File
@@ -1,138 +0,0 @@
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
},
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className,
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
SheetFooter.displayName = 'SheetFooter';
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
@@ -1,15 +0,0 @@
import { cn } from '@/lib/utils';
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn('animate-pulse rounded-md bg-primary/10', className)}
{...props}
/>
);
}
export { Skeleton };
-44
View File
@@ -1,44 +0,0 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const stepsVariants = cva(
'ml-4 mb-12 border-l border-border pl-6 dark:border-border [counter-reset:step] flex flex-col gap-12',
);
interface StepProps {
title?: string;
children: React.ReactNode;
stepNumber?: number; // Add the stepNumber prop
}
const Step = ({ title, children, stepNumber }: StepProps) => (
<div className="relative">
<div className="absolute w-[33px] h-[33px] border-4 border-muted bg-muted rounded-full text-foreground text-base font-normal text-center mt-[3px] ml-[-41px]">
{stepNumber}
</div>
<div className="pl-12">
{title && <h3 className="text-lg font-semibold mb-2">{title}</h3>}
{children}
</div>
</div>
);
const Steps = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof stepsVariants>
>(({ className, children, ...props }, ref) => (
<div ref={ref} className={cn(stepsVariants(), className)} {...props}>
{React.Children.map(children, (child, index) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<any>, {
stepNumber: index + 1,
})
: child,
)}
</div>
));
Steps.displayName = 'Steps';
export { Steps, Step };
-27
View File
@@ -1,27 +0,0 @@
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import { cn } from '@/lib/utils';
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };
-120
View File
@@ -1,120 +0,0 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
));
Table.displayName = 'Table';
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
className,
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-10 px-2 text-left align-middle font-medium text-gray-700 dark:text-gray-300 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn('mt-4 text-sm text-gray-700 dark:text-gray-300', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};
-67
View File
@@ -1,67 +0,0 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const tabsListVariants = cva('', {
variants: {
layout: {
default:
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-gray-700 dark:text-gray-300',
underlined:
'w-full justify-start rounded-none border-b bg-transparent p-0',
},
},
defaultVariants: {
layout: 'default',
},
});
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
layout?: 'default' | 'underlined';
}
>(({ className, layout = 'default', ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(tabsListVariants({ layout }), className)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const tabsTriggerVariants = cva('', {
variants: {
variant: {
underlined:
'relative rounded-none border-b-2 border-b-transparent bg-transparent px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-300 shadow-none transition-none focus-visible:ring-0 data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none ',
default:
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
},
},
defaultVariants: {
variant: 'default',
},
});
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
variant?: 'default' | 'underlined';
}
>(({ className, variant = 'default', ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(tabsTriggerVariants({ variant }), className)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = TabsPrimitive.Content;
export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -1,24 +0,0 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex w-full rounded-md border min-h-9 border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Textarea.displayName = 'Textarea';
export { Textarea };
-127
View File
@@ -1,127 +0,0 @@
import * as React from 'react';
import { Cross2Icon } from '@radix-ui/react-icons';
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
className,
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{
variants: {
variant: {
default: 'border-foreground bg-background text-foreground',
destructive:
'destructive group border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className,
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className,
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn('text-sm font-semibold [&+div]:text-xs', className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};
@@ -1,33 +0,0 @@
import { useToast } from '@/components/hooks/use-toast';
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from '@/components/ui/toast';
export function Toaster() {
const { toasts } = useToast();
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
);
})}
<ToastViewport />
</ToastProvider>
);
}
@@ -1,28 +0,0 @@
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -30,7 +30,7 @@ const ccIcons: Record<string, IconType> = {
link: LuBanknote,
};
export interface PaymentMethodsProps {
interface PaymentMethodsProps {
hasMethods?: boolean;
methods?: TenantPaymentMethod[];
}
@@ -1,6 +1,6 @@
import * as React from 'react';
import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
import type { ToastActionElement, ToastProps } from '@/components/v1/ui/toast';
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
@@ -68,7 +68,7 @@ const addToRemoveQueue = (toastId: string) => {
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
@@ -187,4 +187,4 @@ function useToast() {
};
}
export { useToast, toast };
export { useToast };
@@ -1,360 +0,0 @@
import React, { useMemo, useCallback } from 'react';
import { Group } from '@visx/group';
import { AreaClosed, Line, Bar } from '@visx/shape';
import {
withTooltip,
TooltipWithBounds,
Tooltip,
defaultStyles,
} from '@visx/tooltip';
import { GridRows, GridColumns } from '@visx/grid';
import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip';
import { scaleTime, scaleLinear } from '@visx/scale';
import { AxisLeft, AxisBottom } from '@visx/axis';
import { LinearGradient } from '@visx/gradient';
import { curveMonotoneX } from '@visx/curve';
import { localPoint } from '@visx/event';
import { max, extent, bisector } from '@visx/vendor/d3-array';
import { timeFormat } from '@visx/vendor/d3-time-format';
import { Text } from '@visx/text';
const getDate = (d: MetricValue) => d.date;
const getValue = (d: MetricValue) => d.value;
// format to 2 decimal places
export const format2Dec = (d: number) => {
if (!d.toFixed) {
return '0.00';
}
return `${d.toFixed(2)}`;
};
const bisectDate = bisector<MetricValue, Date>((d) => d.date).left;
export interface MetricValue {
date: Date;
value: number;
}
type TooltipData = MetricValue;
const formatDate = timeFormat('%y-%m-%d %I:%M:%S');
const accentColor = '#ffffff44';
const background = '#1E293B';
const background2 = '#8c77e0';
const accentColorDark = '#8c77e0';
const tooltipStyles = {
...defaultStyles,
border: '1px solid white',
color: 'white',
background,
};
const axisColor = '#cecece';
const axisBottomTickLabelProps = {
textAnchor: 'middle' as const,
fontFamily: 'Arial',
fontSize: 10,
fill: axisColor,
};
const axisLeftTickLabelProps = {
dx: '-0.25em',
dy: '0.25em',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'end' as const,
fill: axisColor,
};
export const formatPercentTooltip = (d: number) => `${format2Dec(d)}%`;
type AreaChartProps = {
data: MetricValue[];
kind: 'area' | 'bar';
gradientColor?: string;
width: number;
height: number;
hideBottomAxis?: boolean;
hideLeftAxis?: boolean;
children?: React.ReactNode;
yLabel?: string;
xLabel?: string;
yDomain?: [number, number];
xDomain?: [Date, Date];
centerText?: string;
tooltipFormat?: (d: number) => string;
};
export default withTooltip<AreaChartProps, TooltipData>(
({
data,
kind,
gradientColor = background2,
width,
height,
hideBottomAxis = false,
hideLeftAxis = false,
children,
yLabel,
xLabel,
yDomain,
xDomain,
centerText,
showTooltip,
hideTooltip,
tooltipFormat,
tooltipData,
tooltipTop = 0,
tooltipLeft = 0,
}: AreaChartProps & WithTooltipProvidedProps<TooltipData>) => {
if (width < 10) {
return null;
}
const innerWidth = width;
const innerHeight = height;
const dateScale = useMemo(
() =>
scaleTime<number>({
range: [0, width],
domain: xDomain || (extent(data, getDate) as [Date, Date]),
}),
[width, data, xDomain],
);
const yScale = useMemo(
() =>
scaleLinear<number>({
range: [height, 0],
domain: yDomain || [0, 1.3 * (max(data, getValue) || 0)],
nice: true,
}),
[height, data, yDomain],
);
const handleTooltip = useCallback(
(
event:
| React.TouchEvent<SVGRectElement>
| React.MouseEvent<SVGRectElement>,
) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = dateScale.invert(x);
const index = bisectDate(data, x0, 1);
const d0 = data[index - 1];
const d1 = data[index];
let d = d0;
if (d1 && getDate(d1)) {
d =
x0.valueOf() - getDate(d0).valueOf() >
getDate(d1).valueOf() - x0.valueOf()
? d1
: d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: yScale(getValue(d)),
});
},
[showTooltip, yScale, dateScale, data],
);
let barWidth = innerWidth / data.length;
if (barWidth <= 5) {
barWidth = 6;
}
return (
<div>
<svg width={width} height={height} overflow={'visible'}>
{centerText && (
<Text className="fill-foreground" x="50%" y="50%" dx={-200}>
{centerText}
</Text>
)}
<rect
x={0}
y={0}
width={innerWidth}
height={innerHeight}
fill="url(#area-background-gradient)"
rx={14}
/>
<GridRows
left={0}
scale={yScale}
width={innerWidth}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.6}
pointerEvents="none"
/>
<GridColumns
top={0}
left={0}
scale={dateScale}
width={innerWidth}
height={innerHeight}
strokeDasharray="1,3"
stroke={accentColor}
strokeOpacity={0.6}
pointerEvents="none"
/>
<Group height={height} width={width}>
<LinearGradient
id="gradient"
from={gradientColor}
fromOpacity={1}
to={gradientColor}
toOpacity={0.2}
height={innerHeight}
/>
{kind == 'bar' &&
data.map((d, i) => {
if (i == 0) {
return (
<Bar
key={i}
x={dateScale(getDate(d)) || 0}
y={yScale(getValue(d)) || 0}
width={(barWidth - 4) / 2}
height={innerHeight - yScale(getValue(d)) || 0}
fill="url(#gradient)"
rx={2}
/>
);
}
return (
<Bar
key={i}
x={(dateScale(getDate(d)) || 0) - barWidth / 2}
y={yScale(getValue(d)) || 0}
width={barWidth - 4}
height={innerHeight - yScale(getValue(d)) || 0}
fill="url(#gradient)"
rx={2}
/>
);
})}
{kind == 'area' && (
<AreaClosed<MetricValue>
data={data}
x={(d) => dateScale(d.date) || 0}
y={(d) => yScale(d.value) || 0}
yScale={yScale}
strokeWidth={1}
stroke="url(#gradient)"
fill="url(#gradient)"
curve={curveMonotoneX}
height={innerHeight}
/>
)}
{!hideBottomAxis && (
<AxisBottom
top={height}
scale={dateScale}
numTicks={width > 520 ? 10 : 5}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={axisBottomTickLabelProps}
label={xLabel}
/>
)}
{!hideLeftAxis && (
<AxisLeft
scale={yScale}
numTicks={5}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={axisLeftTickLabelProps}
label={yLabel}
labelClassName="text-white fill-foreground"
/>
)}
{children}
</Group>
<Bar
x={0}
y={0}
width={innerWidth}
height={innerHeight}
fill="transparent"
rx={14}
onTouchStart={handleTooltip}
onTouchMove={handleTooltip}
onMouseMove={handleTooltip}
onMouseLeave={() => hideTooltip()}
/>
{data.length > 0 && tooltipData && (
<g>
<Line
from={{ x: tooltipLeft, y: 0 }}
to={{ x: tooltipLeft, y: innerHeight + 0 }}
stroke={accentColorDark}
strokeWidth={2}
pointerEvents="none"
strokeDasharray="5,2"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop + 1}
r={4}
fill="black"
fillOpacity={0.1}
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill={accentColorDark}
stroke="white"
strokeWidth={2}
pointerEvents="none"
/>
</g>
)}
</svg>
{data.length > 0 && tooltipData && (
<div>
<TooltipWithBounds
key={Math.random()}
top={tooltipTop - 24}
left={tooltipLeft}
style={tooltipStyles}
>
{tooltipFormat
? tooltipFormat(getValue(tooltipData))
: getValue(tooltipData)}
</TooltipWithBounds>
<Tooltip
top={innerHeight - 14}
left={tooltipLeft}
style={{
...defaultStyles,
minWidth: 72,
textAlign: 'center',
transform: 'translateX(-50%)',
}}
>
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
)}
</div>
);
},
);
@@ -1,6 +1,5 @@
import * as React from 'react';
import { CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons';
import { Column } from '@tanstack/react-table';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/v1/ui/badge';
@@ -27,17 +26,6 @@ import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>;
title?: string;
type?: ToolbarType;
options?: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
const keyValuePairSchema = z.object({
key: z.string().min(1, 'Key is required'),
value: z.string().min(1, 'Value is required'),
@@ -50,23 +38,6 @@ const arrayInputSchema = z.object({
type KeyValuePair = z.infer<typeof keyValuePairSchema>;
type ArrayInput = z.infer<typeof arrayInputSchema>;
export function DataTableFacetedFilter<TData, TValue>({
column,
title,
type = ToolbarType.Checkbox,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
return (
<Combobox
values={column?.getFilterValue() as string[]}
title={title}
type={type}
options={options}
setValues={(values) => column?.setFilterValue(values)}
/>
);
}
export function Combobox({
values = [],
title,
@@ -1,54 +0,0 @@
import * as React from 'react';
import { Column } from '@tanstack/react-table';
import { ToolbarType } from './data-table-toolbar';
import { Combobox } from '../combobox/combobox';
import { Label } from '../../ui/label';
import { Checkbox } from '../../ui/checkbox';
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>;
title?: string;
type?: ToolbarType;
options?: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
export function DataTableFacetedFilter<TData, TValue>({
column,
title,
type = ToolbarType.Checkbox,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
const value = column?.getFilterValue();
if (type === ToolbarType.Switch) {
return (
<div className="flex items-center space-x-2 border p-2 rounded-md border-dashed h-[32px]">
<Checkbox
id="toolbar-switch"
checked={!!value}
onCheckedChange={(e) =>
column?.setFilterValue(e.valueOf() === true ? true : undefined)
}
/>
<Label htmlFor="toolbar-switch" className="text-xs">
Flatten
</Label>
</div>
);
}
return (
<Combobox
values={typeof value === 'string' ? [value] : (value as string[])}
title={title}
type={type}
options={options}
setValues={(values) => column?.setFilterValue(values)}
/>
);
}
@@ -1,64 +0,0 @@
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
import { MixerHorizontalIcon } from '@radix-ui/react-icons';
import { Table } from '@tanstack/react-table';
import { Button } from '@/components/v1/ui/button';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from '@/components/v1/ui/dropdown-menu';
interface DataTableViewOptionsProps<TData> {
table: Table<TData>;
// todo: make this required
columnKeyToName?: Record<string, string>;
}
export function DataTableViewOptions<TData>({
table,
columnKeyToName,
}: DataTableViewOptionsProps<TData>) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[180px] z-[70]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== 'undefined' && column.getCanHide(),
)
.map((column) => {
const columnName =
(columnKeyToName ?? {})[
column.id as keyof typeof columnKeyToName
] || column.id;
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{columnName}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -15,7 +15,7 @@ import {
CommandItem,
CommandList,
} from '@/components/v1/ui/command';
import { Tenant, TenantMember, TenantVersion } from '@/lib/api';
import { Tenant, TenantMember } from '@/lib/api';
import { OrganizationForUser } from '@/lib/api/generated/cloud/data-contracts';
import { CaretSortIcon } from '@radix-ui/react-icons';
import {
@@ -196,13 +196,6 @@ export function OrganizationSelector({
const handleTenantSelect = (tenant: Tenant) => {
setCurrTenant(tenant);
if (tenant.version === TenantVersion.V0) {
// Hack to wait for next event loop tick so local storage is updated
setTimeout(() => {
window.location.href = `/workflow-runs?tenant=${tenant.metadata.id}`;
}, 0);
}
};
const toggleOrgExpansion = (orgId: string) => {
@@ -14,7 +14,7 @@ import {
CommandSeparator,
} from '@/components/v1/ui/command';
import { Link } from 'react-router-dom';
import { TenantMember, TenantVersion } from '@/lib/api';
import { TenantMember } from '@/lib/api';
import { CaretSortIcon, PlusCircledIcon } from '@radix-ui/react-icons';
import {
PopoverTrigger,
@@ -70,13 +70,6 @@ export function TenantSwitcher({
invariant(membership.tenant);
setCurrTenant(membership.tenant);
setOpen(false);
if (membership.tenant.version === TenantVersion.V0) {
// Hack to wait for next event loop tick so local storage is updated
setTimeout(() => {
window.location.href = `/workflow-runs?tenant=${membership.tenant?.metadata.id}`;
}, 0);
}
}}
value={membership.tenant?.slug}
className="text-sm cursor-pointer"
@@ -1,61 +0,0 @@
import { useTenantDetails } from '@/hooks/use-tenant';
import { User } from '@/lib/api';
import useApiMeta from '@/pages/auth/hooks/use-api-meta';
import React, { PropsWithChildren, useEffect, useMemo } from 'react';
interface SupportChatProps {
user: User;
}
const SupportChat: React.FC<PropsWithChildren & SupportChatProps> = ({
user,
children,
}) => {
const meta = useApiMeta();
const { tenant } = useTenantDetails();
const APP_ID = useMemo(() => {
if (!meta.data?.pylonAppId) {
return null;
}
return meta.data.pylonAppId;
}, [meta]);
useEffect(() => {
if (!APP_ID) {
return;
}
const pylonScript = `(function(){var e=window;var t=document;var n=function(){n.e(arguments)};n.q=[];n.e=function(e){n.q.push(e)};e.Pylon=n;var r=function(){var e=t.createElement("script");e.setAttribute("type","text/javascript");e.setAttribute("async","true");e.setAttribute("src","https://widget.usepylon.com/widget/${APP_ID}");var n=t.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};if(t.readyState==="complete"){r()}else if(e.addEventListener){e.addEventListener("load",r,false)}})();`;
document.body.appendChild(document.createElement('script')).innerHTML =
pylonScript;
}, [APP_ID]);
useEffect(() => {
if (!APP_ID || !user) {
return;
}
(window as any).pylon = {
chat_settings: {
app_id: APP_ID,
email: user.email,
name: user.name,
email_hash: user.emailHash,
},
};
(window as any).Pylon('setNewIssueCustomFields', {
user_id: user.metadata.id,
tenant_name: tenant?.name,
tenant_slug: tenant?.slug,
tenant_id: tenant?.metadata?.id,
});
}, [user, APP_ID, tenant]);
return children;
};
export default SupportChat;
@@ -10,7 +10,7 @@ import {
setDateByType,
} from './time-picker-utils';
export interface TimePickerInputProps
interface TimePickerInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
picker: TimePickerType;
date: Date | undefined;
@@ -1,27 +1,27 @@
/**
* regular expression to check for valid hour format (01-23)
*/
export function isValidHour(value: string) {
function isValidHour(value: string) {
return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
}
/**
* regular expression to check for valid 12 hour format (01-12)
*/
export function isValid12Hour(value: string) {
function isValid12Hour(value: string) {
return /^(0[1-9]|1[0-2])$/.test(value);
}
/**
* regular expression to check for valid minute format (00-59)
*/
export function isValidMinuteOrSecond(value: string) {
function isValidMinuteOrSecond(value: string) {
return /^[0-5][0-9]$/.test(value);
}
type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };
export function getValidNumber(
function getValidNumber(
value: string,
{ max, min = 0, loop = false }: GetValidNumberConfig,
) {
@@ -49,21 +49,21 @@ export function getValidNumber(
return '00';
}
export function getValidHour(value: string) {
function getValidHour(value: string) {
if (isValidHour(value)) {
return value;
}
return getValidNumber(value, { max: 23 });
}
export function getValid12Hour(value: string) {
function getValid12Hour(value: string) {
if (isValid12Hour(value)) {
return value;
}
return getValidNumber(value, { min: 1, max: 12 });
}
export function getValidMinuteOrSecond(value: string) {
function getValidMinuteOrSecond(value: string) {
if (isValidMinuteOrSecond(value)) {
return value;
}
@@ -76,7 +76,7 @@ type GetValidArrowNumberConfig = {
step: number;
};
export function getValidArrowNumber(
function getValidArrowNumber(
value: string,
{ min, max, step }: GetValidArrowNumberConfig,
) {
@@ -88,37 +88,37 @@ export function getValidArrowNumber(
return '00';
}
export function getValidArrowHour(value: string, step: number) {
function getValidArrowHour(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 23, step });
}
export function getValidArrow12Hour(value: string, step: number) {
function getValidArrow12Hour(value: string, step: number) {
return getValidArrowNumber(value, { min: 1, max: 12, step });
}
export function getValidArrowMinuteOrSecond(value: string, step: number) {
function getValidArrowMinuteOrSecond(value: string, step: number) {
return getValidArrowNumber(value, { min: 0, max: 59, step });
}
export function setMinutes(date: Date, value: string) {
function setMinutes(date: Date, value: string) {
const minutes = getValidMinuteOrSecond(value);
date.setMinutes(parseInt(minutes, 10));
return date;
}
export function setSeconds(date: Date, value: string) {
function setSeconds(date: Date, value: string) {
const seconds = getValidMinuteOrSecond(value);
date.setSeconds(parseInt(seconds, 10));
return date;
}
export function setHours(date: Date, value: string) {
function setHours(date: Date, value: string) {
const hours = getValidHour(value);
date.setHours(parseInt(hours, 10));
return date;
}
export function set12Hours(date: Date, value: string, period: Period) {
function set12Hours(date: Date, value: string, period: Period) {
const hours = parseInt(getValid12Hour(value), 10);
const convertedHours = convert12HourTo24Hour(hours, period);
date.setHours(convertedHours);
@@ -191,7 +191,7 @@ export function getArrowByType(
* 12:00 PM is 12:00
* 12:00 AM is 00:00
*/
export function convert12HourTo24Hour(hour: number, period: Period) {
function convert12HourTo24Hour(hour: number, period: Period) {
if (period === 'PM') {
if (hour <= 11) {
return hour + 12;
@@ -212,7 +212,7 @@ export function convert12HourTo24Hour(hour: number, period: Period) {
* but needs to be displayed to the user
* in its 12-hour representation
*/
export function display12HourValue(hours: number) {
function display12HourValue(hours: number) {
if (hours === 0 || hours === 12) {
return '12';
}
@@ -12,10 +12,7 @@ import {
import { V1TaskStatus } from '@/lib/api';
import { Duration as DateFnsDuration } from 'date-fns';
export function formatDuration(
duration: DateFnsDuration,
rawTimeMs: number,
): string {
function formatDuration(duration: DateFnsDuration, rawTimeMs: number): string {
const parts = [];
if (duration.days) {
@@ -48,7 +45,7 @@ export function formatDuration(
return parts.join(' ');
}
export const isValidTimestamp = (
const isValidTimestamp = (
timestamp?: string | Date | null,
): timestamp is string | Date => {
if (!timestamp) {
@@ -78,7 +75,7 @@ const durationVariants = cva('text-sm', {
},
});
export interface DurationProps
interface DurationProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof durationVariants> {
start?: string | Date | null;
+1 -1
View File
@@ -43,4 +43,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
);
}
export { Badge, badgeVariants };
export { Badge };
@@ -111,5 +111,4 @@ export {
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
@@ -5,7 +5,7 @@ import { DayPicker } from 'react-day-picker';
import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/v1/ui/button';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({
className,
+2 -9
View File
@@ -254,7 +254,7 @@ const ChartTooltipContent = React.forwardRef<
);
ChartTooltipContent.displayName = 'ChartTooltip';
const ChartLegend = RechartsPrimitive.Legend;
export const ChartLegend = RechartsPrimitive.Legend;
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
@@ -353,11 +353,4 @@ function getPayloadConfigFromPayload(
: config[key as keyof typeof config];
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
};
export { ChartContainer, ChartTooltip, ChartTooltipContent };
@@ -1,6 +1,6 @@
import CopyToClipboard from './copy-to-clipboard';
import { cn } from '@/lib/utils';
import Editor, { DiffEditor, Monaco } from '@monaco-editor/react';
import Editor, { Monaco } from '@monaco-editor/react';
import 'monaco-themes/themes/Pastels on Dark.json';
import { useTheme } from '@/components/theme-provider';
@@ -81,63 +81,6 @@ export function CodeEditor({
);
}
export function DiffCodeEditor({
code = '',
setCode,
language,
className,
height,
width,
copy,
originalValue = '',
wrapLines = true,
}: CodeEditorProps & {
originalValue: string;
}) {
const setEditorTheme = (monaco: Monaco) => {
monaco.editor.defineTheme('pastels-on-dark', getMonacoTheme());
monaco.editor.setTheme('pastels-on-dark');
};
return (
<div
className={cn(
className,
'w-full h-fit relative rounded-lg overflow-hidden',
)}
>
<DiffEditor
beforeMount={setEditorTheme}
language={language}
width={width || '100%'}
height={height || '400px'}
theme="pastels-on-dark"
original={originalValue}
modified={code || ''}
options={{
minimap: { enabled: false },
wordWrap: wrapLines ? 'on' : 'off',
lineNumbers: 'off',
readOnly: !setCode,
scrollbar: { vertical: 'hidden', horizontal: 'hidden' },
showFoldingControls: language == 'json' ? 'always' : 'never',
lineDecorationsWidth: 0,
overviewRulerBorder: false,
colorDecorators: false,
hideCursorInOverviewRuler: true,
contextmenu: false,
}}
/>
{copy && (
<CopyToClipboard
className="absolute top-2 right-2"
text={code?.trim() || ''}
/>
)}
</div>
);
}
type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black' | 'hc-light';
const getMonacoTheme = () => {
@@ -2,8 +2,8 @@ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
export const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
export { Collapsible, CollapsibleContent };
@@ -23,7 +23,7 @@ Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
export const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
@@ -146,12 +146,10 @@ CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};
@@ -107,8 +107,6 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
@@ -12,13 +12,13 @@ const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
@@ -141,7 +141,7 @@ const DropdownMenuRadioItem = React.forwardRef<
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
export const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
@@ -171,7 +171,7 @@ const DropdownMenuSeparator = React.forwardRef<
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
export const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
@@ -189,15 +189,5 @@ export {
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};
@@ -1,44 +0,0 @@
import { ObjectFieldTemplateProps } from '@rjsf/utils';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/v1/ui/accordion';
import { DEFAULT_COLLAPSED } from '../json-form';
export const CollapsibleSection = (props: ObjectFieldTemplateProps) => {
if (!props.title) {
return props.properties.map((element, i) => (
<div className="property-wrapper ml-4" key={i}>
{element.content}
</div>
));
}
return (
<Accordion
type="single"
collapsible
className="w-full"
defaultValue={DEFAULT_COLLAPSED.includes(props.title) ? 'closed' : 'open'}
>
<AccordionItem value="open">
<AccordionTrigger>{props.title}</AccordionTrigger>
<AccordionContent>
{props.description}
{props.properties?.length > 0 ? (
props.properties.map((element, i) => (
<div className="property-wrapper ml-4" key={i}>
{element.content}
</div>
))
) : (
<div className="ml-4">empty state</div>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
);
};
@@ -1,100 +0,0 @@
import { BaseInputTemplateProps, getInputProps } from '@rjsf/utils';
import {
ChangeEvent,
FocusEvent,
KeyboardEvent,
useContext,
useEffect,
useRef,
} from 'react';
import { JSONFormContext } from '../json-form';
export function DynamicSizeInputTemplate(props: BaseInputTemplateProps) {
const {
schema,
id,
options,
label,
value,
type,
placeholder,
required,
disabled,
readonly,
autofocus,
onChange,
onChangeOverride,
onBlur,
onFocus,
rawErrors,
hideError,
uiSchema,
registry,
formContext,
...rest
} = props;
const ref = useRef<HTMLTextAreaElement>(null);
const { form } = useContext(JSONFormContext);
const onTextChange = ({
target: { value: val },
}: ChangeEvent<HTMLTextAreaElement>) => {
// Use the options.emptyValue if it is specified and newVal is also an empty string
onChange(val === '' ? options.emptyValue || '' : val);
};
const onTextBlur = ({
target: { value: val },
}: FocusEvent<HTMLTextAreaElement>) => onBlur(id, val);
const onTextFocus = ({
target: { value: val },
}: FocusEvent<HTMLTextAreaElement>) => onFocus(id, val);
const inputProps = { ...rest, ...getInputProps(schema, type, options) };
delete inputProps.hideLabel; // hideLabel is not a valid prop for textarea
const setHeight = (e: HTMLTextAreaElement) => {
e.style.height = 'auto';
e.style.height = `${e.scrollHeight}px`;
};
useEffect(() => {
// Call adjustHeight whenever the watched value changes externally
if (!ref.current) {
return;
}
setHeight(ref.current);
}, [ref.current?.value]);
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (!form?.current) {
return;
}
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
form.current.submit();
}
};
return (
<textarea
ref={ref}
id={id}
value={value}
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
autoFocus={autofocus}
className="overflow-y-hidden"
onKeyDown={handleKeyDown}
onChange={
(onChangeOverride as
| ((event: ChangeEvent<HTMLTextAreaElement>) => void)
| undefined) || onTextChange
}
onBlur={onTextBlur}
onFocus={onTextFocus}
{...inputProps}
/>
);
}
@@ -24,4 +24,4 @@ const HoverCardContent = React.forwardRef<
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };
export { HoverCard, HoverCardTrigger };
+1 -2
View File
@@ -2,8 +2,7 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
@@ -1,159 +0,0 @@
import { cn } from '@/lib/utils';
import {
RJSFSchema,
RJSFValidationError,
UiSchema,
ValidationData,
ValidatorType,
} from '@rjsf/utils';
import Form from '@rjsf/core';
import { PlayIcon } from '@radix-ui/react-icons';
import { Button } from './button';
import { Loading } from './loading';
import { CollapsibleSection } from './form-inputs/collapsible-section';
import { DynamicSizeInputTemplate } from './form-inputs/dynamic-size-input-template';
import { createContext, useRef } from 'react';
type JSONPrimitive = string | number | boolean | null | Array<JSONPrimitive>;
export type JSONType = {
[key: string]: JSONType | JSONPrimitive | Array<JSONType>;
};
export const DEFAULT_COLLAPSED = ['advanced', 'user data'];
class NoValidation implements ValidatorType {
validateFormData(): ValidationData<any> {
return { errors: [], errorSchema: {} };
}
toErrorList(): RJSFValidationError[] {
return [];
}
isValid(): boolean {
return true;
}
rawValidation() {
return {};
}
}
interface JSONFormContextSchema {
form?: React.RefObject<Form>;
}
export const JSONFormContext = createContext<JSONFormContextSchema>({
form: undefined,
});
export function JsonForm({
inputSchema,
inputData,
className,
setInput,
disabled,
onSubmit,
}: {
inputSchema: JSONType;
className?: string;
inputData: JSONType;
setInput: React.Dispatch<React.SetStateAction<string>>;
disabled?: boolean;
onSubmit: () => void;
}) {
const formRef = useRef<Form>(null);
const schema = {
...inputSchema,
required: undefined,
$schema: undefined,
properties: {
...(inputSchema.properties as any),
triggered_by: undefined,
advanced: {
// Transform the schema to wrap the triggered by field
type: 'object',
properties: {
triggered_by: inputSchema.properties
? (inputSchema.properties as any).triggered_by
: undefined,
},
},
},
} as RJSFSchema;
delete schema.properties?.triggered_by;
const uiSchema: UiSchema<any, RJSFSchema, any> = {
input: {
'ui:title': 'workflow input',
},
parents: {
'ui:title': 'parent step data',
},
overrides: {
'ui:title': 'step overrides',
},
user_data: {
'ui:title': 'user data',
},
'ui:order': ['input', 'overrides', 'parents', '*'],
};
return (
<JSONFormContext.Provider value={{ form: formRef }}>
<div
className={cn(
className,
'w-full h-fit relative rounded-lg overflow-hidden',
)}
>
<Form
ref={formRef}
formData={inputData}
schema={schema}
disabled={disabled}
templates={{
BaseInputTemplate: DynamicSizeInputTemplate,
ObjectFieldTemplate: CollapsibleSection,
}}
uiSchema={uiSchema}
validator={new NoValidation()}
noHtml5Validate={true}
onChange={(data) => {
// Transform the data to unwrap the advanced fields
const formData = { ...data.formData, ...data.formData.advanced };
delete formData.advanced;
setInput((prev) =>
JSON.stringify({
...JSON.parse(prev),
...formData,
}),
);
}}
onSubmit={onSubmit}
onError={(e) => {
console.error(e);
}}
>
<Button className="w-fit invisible" disabled={disabled}>
{disabled ? (
<>
<Loading />
Playing
</>
) : (
<>
<PlayIcon
className={cn(disabled ? 'rotate-180' : '', 'h-4 w-4 mr-2')}
/>
Play Step
</>
)}
</Button>
</Form>
</div>
</JSONFormContext.Provider>
);
}
@@ -1,238 +0,0 @@
import * as React from 'react';
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from '@radix-ui/react-icons';
import * as MenubarPrimitive from '@radix-ui/react-menubar';
import { cn } from '@/lib/utils';
const MenubarMenu = MenubarPrimitive.Menu;
const MenubarGroup = MenubarPrimitive.Group;
const MenubarPortal = MenubarPrimitive.Portal;
const MenubarSub = MenubarPrimitive.Sub;
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
'flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm',
className,
)}
{...props}
/>
));
Menubar.displayName = MenubarPrimitive.Root.displayName;
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
className,
)}
{...props}
/>
));
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
));
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props },
ref,
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</MenubarPrimitive.Portal>
),
);
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
{...props}
/>
));
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
));
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
));
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className,
)}
{...props}
/>
));
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
'ml-auto text-xs tracking-widest text-gray-700 dark:text-gray-300',
className,
)}
{...props}
/>
);
};
MenubarShortcut.displayname = 'MenubarShortcut';
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
};
@@ -1,46 +0,0 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };
+2 -13
View File
@@ -10,7 +10,7 @@ import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
export const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
@@ -147,15 +147,4 @@ const SelectSeparator = React.forwardRef<
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};
export { Select, SelectValue, SelectTrigger, SelectContent, SelectItem };
-138
View File
@@ -1,138 +0,0 @@
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
},
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-2 text-center sm:text-left',
className,
)}
{...props}
/>
);
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
SheetFooter.displayName = 'SheetFooter';
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

Some files were not shown because too many files have changed in this diff Show More