feat: typed database with kysely, updated config
This commit is contained in:
parent
28ec24b2c2
commit
67da0a72db
24 changed files with 1656 additions and 434 deletions
44
src/components/ui/badge.tsx
Normal file
44
src/components/ui/badge.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import type { Component, ComponentProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } 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",
|
||||
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
||||
outline: "text-foreground",
|
||||
success: "border-success-foreground bg-success text-success-foreground",
|
||||
warning: "border-warning-foreground bg-warning text-warning-foreground",
|
||||
error: "border-error-foreground bg-error text-error-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type BadgeProps = ComponentProps<"div"> &
|
||||
VariantProps<typeof badgeVariants> & {
|
||||
round?: boolean;
|
||||
};
|
||||
|
||||
const Badge: Component<BadgeProps> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class", "variant", "round"]);
|
||||
return (
|
||||
<div
|
||||
class={cn(badgeVariants({ variant: local.variant }), local.round && "rounded-full", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export type { BadgeProps };
|
||||
export { Badge, badgeVariants };
|
51
src/components/ui/button.tsx
Normal file
51
src/components/ui/button.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import type { JSX, ValidComponent } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
import * as ButtonPrimitive from "@kobalte/core/button";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 px-3 text-xs",
|
||||
lg: "h-11 px-8",
|
||||
icon: "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type ButtonProps<T extends ValidComponent = "button"> = ButtonPrimitive.ButtonRootProps<T> &
|
||||
VariantProps<typeof buttonVariants> & { class?: string | undefined; children?: JSX.Element };
|
||||
|
||||
const Button = <T extends ValidComponent = "button">(props: PolymorphicProps<T, ButtonProps<T>>) => {
|
||||
const [local, others] = splitProps(props as ButtonProps, ["variant", "size", "class"]);
|
||||
return (
|
||||
<ButtonPrimitive.Root
|
||||
class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export type { ButtonProps };
|
||||
export { Button, buttonVariants };
|
|
@ -1,19 +1,19 @@
|
|||
import type { Component } from "solid-js"
|
||||
import { createEffect, createSignal, mergeProps, on, onCleanup, onMount } from "solid-js"
|
||||
import { unwrap } from "solid-js/store"
|
||||
import type { Component } from "solid-js";
|
||||
import { createEffect, createSignal, mergeProps, on, onCleanup, onMount } from "solid-js";
|
||||
import { unwrap } from "solid-js/store";
|
||||
|
||||
import type { Ref } from "@solid-primitives/refs"
|
||||
import { mergeRefs } from "@solid-primitives/refs"
|
||||
import type { Ref } from "@solid-primitives/refs";
|
||||
import { mergeRefs } from "@solid-primitives/refs";
|
||||
import type {
|
||||
ChartComponent,
|
||||
ChartData,
|
||||
ChartItem,
|
||||
ChartOptions,
|
||||
Plugin as ChartPlugin,
|
||||
ChartType,
|
||||
ChartTypeRegistry,
|
||||
TooltipModel
|
||||
} from "chart.js"
|
||||
Plugin as ChartPlugin,
|
||||
TooltipModel,
|
||||
} from "chart.js";
|
||||
import {
|
||||
ArcElement,
|
||||
BarController,
|
||||
|
@ -34,167 +34,166 @@ import {
|
|||
RadarController,
|
||||
RadialLinearScale,
|
||||
ScatterController,
|
||||
Tooltip
|
||||
} from "chart.js"
|
||||
Tooltip,
|
||||
} from "chart.js";
|
||||
|
||||
type TypedChartProps = {
|
||||
data: ChartData
|
||||
options?: ChartOptions
|
||||
plugins?: ChartPlugin[]
|
||||
ref?: Ref<HTMLCanvasElement | null>
|
||||
width?: number | undefined
|
||||
height?: number | undefined
|
||||
interface TypedChartProps {
|
||||
data: ChartData;
|
||||
options?: ChartOptions;
|
||||
plugins?: ChartPlugin[];
|
||||
ref?: Ref<HTMLCanvasElement | null>;
|
||||
width?: number | undefined;
|
||||
height?: number | undefined;
|
||||
}
|
||||
|
||||
type ChartProps = TypedChartProps & {
|
||||
type: ChartType
|
||||
}
|
||||
type: ChartType;
|
||||
};
|
||||
|
||||
type ChartContext = {
|
||||
chart: Chart
|
||||
tooltip: TooltipModel<keyof ChartTypeRegistry>
|
||||
interface ChartContext {
|
||||
chart: Chart;
|
||||
tooltip: TooltipModel<keyof ChartTypeRegistry>;
|
||||
}
|
||||
|
||||
const BaseChart: Component<ChartProps> = (rawProps) => {
|
||||
const [canvasRef, setCanvasRef] = createSignal<HTMLCanvasElement | null>()
|
||||
const [chart, setChart] = createSignal<Chart>()
|
||||
const [canvasRef, setCanvasRef] = createSignal<HTMLCanvasElement | null>();
|
||||
const [chart, setChart] = createSignal<Chart>();
|
||||
|
||||
const props = mergeProps(
|
||||
{
|
||||
width: 512,
|
||||
height: 512,
|
||||
options: { responsive: true } as ChartOptions,
|
||||
plugins: [] as ChartPlugin[]
|
||||
plugins: [] as ChartPlugin[],
|
||||
},
|
||||
rawProps
|
||||
)
|
||||
rawProps,
|
||||
);
|
||||
|
||||
const init = () => {
|
||||
const ctx = canvasRef()?.getContext("2d") as ChartItem
|
||||
const config = unwrap(props)
|
||||
const ctx = canvasRef()?.getContext("2d") as ChartItem;
|
||||
const config = unwrap(props);
|
||||
const chart = new Chart(ctx, {
|
||||
type: config.type,
|
||||
data: config.data,
|
||||
options: config.options,
|
||||
plugins: config.plugins
|
||||
})
|
||||
setChart(chart)
|
||||
}
|
||||
plugins: config.plugins,
|
||||
});
|
||||
setChart(chart);
|
||||
};
|
||||
|
||||
onMount(() => init())
|
||||
onMount(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.data,
|
||||
() => {
|
||||
chart()!.data = props.data
|
||||
chart()!.update()
|
||||
chart()!.data = props.data;
|
||||
chart()!.update();
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
{ defer: true },
|
||||
),
|
||||
);
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.options,
|
||||
() => {
|
||||
chart()!.options = props.options
|
||||
chart()!.update()
|
||||
chart()!.options = props.options;
|
||||
chart()!.update();
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
{ defer: true },
|
||||
),
|
||||
);
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
[() => props.width, () => props.height],
|
||||
() => {
|
||||
chart()!.resize(props.width, props.height)
|
||||
chart()!.resize(props.width, props.height);
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
{ defer: true },
|
||||
),
|
||||
);
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.type,
|
||||
() => {
|
||||
const dimensions = [chart()!.width, chart()!.height]
|
||||
chart()!.destroy()
|
||||
init()
|
||||
chart()!.resize(...dimensions)
|
||||
const dimensions = [chart()!.width, chart()!.height];
|
||||
chart()!.destroy();
|
||||
init();
|
||||
chart()!.resize(...dimensions);
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
{ defer: true },
|
||||
),
|
||||
);
|
||||
|
||||
onCleanup(() => {
|
||||
chart()?.destroy()
|
||||
mergeRefs(props.ref, null)
|
||||
})
|
||||
chart()?.destroy();
|
||||
mergeRefs(props.ref, null);
|
||||
});
|
||||
|
||||
Chart.register(Colors, Filler, Legend, Tooltip)
|
||||
Chart.register(Colors, Filler, Legend, Tooltip);
|
||||
return (
|
||||
<canvas
|
||||
ref={mergeRefs(props.ref, (el) => setCanvasRef(el))}
|
||||
height={props.height}
|
||||
width={props.width}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function showTooltip(context: ChartContext) {
|
||||
let el = document.getElementById("chartjs-tooltip")
|
||||
let el = document.getElementById("chartjs-tooltip");
|
||||
if (!el) {
|
||||
el = document.createElement("div")
|
||||
el.id = "chartjs-tooltip"
|
||||
document.body.appendChild(el)
|
||||
el = document.createElement("div");
|
||||
el.id = "chartjs-tooltip";
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
||||
const model = context.tooltip
|
||||
const model = context.tooltip;
|
||||
if (model.opacity === 0 || !model.body) {
|
||||
el.style.opacity = "0"
|
||||
return
|
||||
el.style.opacity = "0";
|
||||
return;
|
||||
}
|
||||
|
||||
el.className = `p-2 bg-card text-card-foreground rounded-lg border shadow-sm text-sm ${
|
||||
model.yAlign ?? `no-transform`
|
||||
}`
|
||||
}`;
|
||||
|
||||
let content = ""
|
||||
let content = "";
|
||||
|
||||
model.title.forEach((title) => {
|
||||
content += `<h3 class="font-semibold leading-none tracking-tight">${title}</h3>`
|
||||
})
|
||||
content += `<h3 class="font-semibold leading-none tracking-tight">${title}</h3>`;
|
||||
});
|
||||
|
||||
content += `<div class="mt-1 text-muted-foreground">`
|
||||
const body = model.body.flatMap((body) => body.lines)
|
||||
content += `<div class="mt-1 text-muted-foreground">`;
|
||||
const body = model.body.flatMap((body) => body.lines);
|
||||
body.forEach((line, i) => {
|
||||
const colors = model.labelColors[i]
|
||||
const colors = model.labelColors[i];
|
||||
content += `
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block h-2 w-2 mr-1 rounded-full border" style="background: ${colors.backgroundColor}; border-color: ${colors.borderColor}"></span>
|
||||
${line}
|
||||
</div>`
|
||||
})
|
||||
content += `</div>`
|
||||
</div>`;
|
||||
});
|
||||
content += `</div>`;
|
||||
|
||||
el.innerHTML = content
|
||||
el.innerHTML = content;
|
||||
|
||||
const pos = context.chart.canvas.getBoundingClientRect()
|
||||
el.style.opacity = "1"
|
||||
el.style.position = "absolute"
|
||||
el.style.left = `${pos.left + window.scrollX + model.caretX}px`
|
||||
el.style.top = `${pos.top + window.scrollY + model.caretY}px`
|
||||
el.style.pointerEvents = "none"
|
||||
const pos = context.chart.canvas.getBoundingClientRect();
|
||||
el.style.opacity = "1";
|
||||
el.style.position = "absolute";
|
||||
el.style.left = `${pos.left + window.scrollX + model.caretX}px`;
|
||||
el.style.top = `${pos.top + window.scrollY + model.caretY}px`;
|
||||
el.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
function createTypedChart(
|
||||
type: ChartType,
|
||||
components: ChartComponent[]
|
||||
): Component<TypedChartProps> {
|
||||
const chartsWithScales: ChartType[] = ["bar", "line", "scatter"]
|
||||
const chartsWithLegends: ChartType[] = ["bar", "line"]
|
||||
function createTypedChart(type: ChartType, components: ChartComponent[]): Component<TypedChartProps> {
|
||||
const chartsWithScales: ChartType[] = ["bar", "line", "scatter"];
|
||||
const chartsWithLegends: ChartType[] = ["bar", "line"];
|
||||
|
||||
const options: ChartOptions = {
|
||||
responsive: true,
|
||||
|
@ -203,18 +202,18 @@ function createTypedChart(
|
|||
? {
|
||||
x: {
|
||||
border: { display: false },
|
||||
grid: { display: false }
|
||||
grid: { display: false },
|
||||
},
|
||||
y: {
|
||||
border: {
|
||||
dash: [3],
|
||||
dashOffset: 3,
|
||||
display: false
|
||||
display: false,
|
||||
},
|
||||
grid: {
|
||||
color: "hsla(240, 3.8%, 46.1%, 0.4)"
|
||||
}
|
||||
}
|
||||
color: "hsla(240, 3.8%, 46.1%, 0.4)",
|
||||
},
|
||||
},
|
||||
}
|
||||
: {},
|
||||
plugins: {
|
||||
|
@ -227,66 +226,61 @@ function createTypedChart(
|
|||
boxWidth: 6,
|
||||
boxHeight: 6,
|
||||
color: "hsl(240, 3.8%, 46.1%)",
|
||||
font: { size: 14 }
|
||||
}
|
||||
font: { size: 14 },
|
||||
},
|
||||
}
|
||||
: { display: false },
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
external: (context) => showTooltip(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
external: (context) => {
|
||||
showTooltip(context);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Chart.register(...components)
|
||||
return (props) => <BaseChart type={type} options={options} {...props} />
|
||||
Chart.register(...components);
|
||||
return (props) => (
|
||||
<BaseChart
|
||||
type={type}
|
||||
options={options}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const BarChart = /* #__PURE__ */ createTypedChart("bar", [
|
||||
BarController,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
LinearScale
|
||||
])
|
||||
const BubbleChart = /* #__PURE__ */ createTypedChart("bubble", [
|
||||
BubbleController,
|
||||
PointElement,
|
||||
LinearScale
|
||||
])
|
||||
const DonutChart = /* #__PURE__ */ createTypedChart("doughnut", [DoughnutController, ArcElement])
|
||||
const BarChart = /* #__PURE__ */ createTypedChart("bar", [BarController, BarElement, CategoryScale, LinearScale]);
|
||||
const BubbleChart = /* #__PURE__ */ createTypedChart("bubble", [BubbleController, PointElement, LinearScale]);
|
||||
const DonutChart = /* #__PURE__ */ createTypedChart("doughnut", [DoughnutController, ArcElement]);
|
||||
const LineChart = /* #__PURE__ */ createTypedChart("line", [
|
||||
LineController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
LinearScale
|
||||
])
|
||||
const PieChart = /* #__PURE__ */ createTypedChart("pie", [PieController, ArcElement])
|
||||
LinearScale,
|
||||
]);
|
||||
const PieChart = /* #__PURE__ */ createTypedChart("pie", [PieController, ArcElement]);
|
||||
const PolarAreaChart = /* #__PURE__ */ createTypedChart("polarArea", [
|
||||
PolarAreaController,
|
||||
ArcElement,
|
||||
RadialLinearScale
|
||||
])
|
||||
RadialLinearScale,
|
||||
]);
|
||||
const RadarChart = /* #__PURE__ */ createTypedChart("radar", [
|
||||
RadarController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
RadialLinearScale
|
||||
])
|
||||
const ScatterChart = /* #__PURE__ */ createTypedChart("scatter", [
|
||||
ScatterController,
|
||||
PointElement,
|
||||
LinearScale
|
||||
])
|
||||
RadialLinearScale,
|
||||
]);
|
||||
const ScatterChart = /* #__PURE__ */ createTypedChart("scatter", [ScatterController, PointElement, LinearScale]);
|
||||
|
||||
export {
|
||||
BaseChart as Chart,
|
||||
BarChart,
|
||||
BubbleChart,
|
||||
BaseChart as Chart,
|
||||
DonutChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
PolarAreaChart,
|
||||
RadarChart,
|
||||
ScatterChart
|
||||
}
|
||||
ScatterChart,
|
||||
};
|
||||
|
|
59
src/components/ui/checkbox.tsx
Normal file
59
src/components/ui/checkbox.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import type { ValidComponent } from "solid-js";
|
||||
import { Match, splitProps, Switch } from "solid-js";
|
||||
|
||||
import * as CheckboxPrimitive from "@kobalte/core/checkbox";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
type CheckboxRootProps<T extends ValidComponent = "div"> = CheckboxPrimitive.CheckboxRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const Checkbox = <T extends ValidComponent = "div">(props: PolymorphicProps<T, CheckboxRootProps<T>>) => {
|
||||
const [local, others] = splitProps(props as CheckboxRootProps, ["class"]);
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn("items-top group relative flex space-x-2", local.class)}
|
||||
{...others}
|
||||
>
|
||||
<CheckboxPrimitive.Input class="peer" />
|
||||
<CheckboxPrimitive.Control class="size-4 shrink-0 rounded-sm border border-primary ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 data-[checked]:border-none data-[indeterminate]:border-none data-[checked]:bg-primary data-[indeterminate]:bg-primary data-[checked]:text-primary-foreground data-[indeterminate]:text-primary-foreground">
|
||||
<CheckboxPrimitive.Indicator>
|
||||
<Switch>
|
||||
<Match when={!others.indeterminate}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
</Match>
|
||||
<Match when={others.indeterminate}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l14 0" />
|
||||
</svg>
|
||||
</Match>
|
||||
</Switch>
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Control>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export { Checkbox };
|
19
src/components/ui/label.tsx
Normal file
19
src/components/ui/label.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import type { Component, ComponentProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const Label: Component<ComponentProps<"label">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<label
|
||||
class={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Label };
|
91
src/components/ui/table.tsx
Normal file
91
src/components/ui/table.tsx
Normal file
|
@ -0,0 +1,91 @@
|
|||
import type { Component, ComponentProps } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const Table: Component<ComponentProps<"table">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<div class="relative w-full overflow-auto">
|
||||
<table
|
||||
class={cn("w-full caption-bottom text-sm", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TableHeader: Component<ComponentProps<"thead">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<thead
|
||||
class={cn("[&_tr]:border-b", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableBody: Component<ComponentProps<"tbody">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<tbody
|
||||
class={cn("[&_tr:last-child]:border-0", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableFooter: Component<ComponentProps<"tfoot">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<tfoot
|
||||
class={cn("bg-primary font-medium text-primary-foreground", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableRow: Component<ComponentProps<"tr">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<tr
|
||||
class={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableHead: Component<ComponentProps<"th">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<th
|
||||
class={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableCell: Component<ComponentProps<"td">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<td
|
||||
class={cn("p-2 align-middle [&:has([role=checkbox])]:pr-0", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableCaption: Component<ComponentProps<"caption">> = (props) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<caption
|
||||
class={cn("mt-4 text-sm text-muted-foreground", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
147
src/components/ui/text-field.tsx
Normal file
147
src/components/ui/text-field.tsx
Normal file
|
@ -0,0 +1,147 @@
|
|||
import type { ValidComponent } from "solid-js";
|
||||
import { mergeProps, splitProps } from "solid-js";
|
||||
|
||||
import type { PolymorphicProps } from "@kobalte/core";
|
||||
import * as TextFieldPrimitive from "@kobalte/core/text-field";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
type TextFieldRootProps<T extends ValidComponent = "div"> = TextFieldPrimitive.TextFieldRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextField = <T extends ValidComponent = "div">(props: PolymorphicProps<T, TextFieldRootProps<T>>) => {
|
||||
const [local, others] = splitProps(props as TextFieldRootProps, ["class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.Root
|
||||
class={cn("flex flex-col gap-1", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldInputProps<T extends ValidComponent = "input"> = TextFieldPrimitive.TextFieldInputProps<T> & {
|
||||
class?: string | undefined;
|
||||
type?:
|
||||
| "button"
|
||||
| "checkbox"
|
||||
| "color"
|
||||
| "date"
|
||||
| "datetime-local"
|
||||
| "email"
|
||||
| "file"
|
||||
| "hidden"
|
||||
| "image"
|
||||
| "month"
|
||||
| "number"
|
||||
| "password"
|
||||
| "radio"
|
||||
| "range"
|
||||
| "reset"
|
||||
| "search"
|
||||
| "submit"
|
||||
| "tel"
|
||||
| "text"
|
||||
| "time"
|
||||
| "url"
|
||||
| "week";
|
||||
};
|
||||
|
||||
const TextFieldInput = <T extends ValidComponent = "input">(rawProps: PolymorphicProps<T, TextFieldInputProps<T>>) => {
|
||||
const props = mergeProps<TextFieldInputProps<T>[]>({ type: "text" }, rawProps);
|
||||
const [local, others] = splitProps(props as TextFieldInputProps, ["type", "class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.Input
|
||||
type={local.type}
|
||||
class={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[invalid]:border-error-foreground data-[invalid]:text-error-foreground",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldTextAreaProps<T extends ValidComponent = "textarea"> = TextFieldPrimitive.TextFieldTextAreaProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldTextArea = <T extends ValidComponent = "textarea">(
|
||||
props: PolymorphicProps<T, TextFieldTextAreaProps<T>>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldTextAreaProps, ["class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.TextArea
|
||||
class={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
label: "data-[invalid]:text-destructive",
|
||||
description: "font-normal text-muted-foreground",
|
||||
error: "text-xs text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "label",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type TextFieldLabelProps<T extends ValidComponent = "label"> = TextFieldPrimitive.TextFieldLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldLabel = <T extends ValidComponent = "label">(props: PolymorphicProps<T, TextFieldLabelProps<T>>) => {
|
||||
const [local, others] = splitProps(props as TextFieldLabelProps, ["class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.Label
|
||||
class={cn(labelVariants(), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldDescriptionProps<T extends ValidComponent = "div"> = TextFieldPrimitive.TextFieldDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldDescription = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, TextFieldDescriptionProps<T>>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldDescriptionProps, ["class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.Description
|
||||
class={cn(labelVariants({ variant: "description" }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldErrorMessageProps<T extends ValidComponent = "div"> = TextFieldPrimitive.TextFieldErrorMessageProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldErrorMessage = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, TextFieldErrorMessageProps<T>>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldErrorMessageProps, ["class"]);
|
||||
return (
|
||||
<TextFieldPrimitive.ErrorMessage
|
||||
class={cn(labelVariants({ variant: "error" }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { TextField, TextFieldDescription, TextFieldErrorMessage, TextFieldInput, TextFieldLabel, TextFieldTextArea };
|
Loading…
Add table
Add a link
Reference in a new issue