@solidpb/ui-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/components/Button/Button.d.ts +12 -0
- package/dist/components/Button/Button.jsx +93 -0
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.js +1 -0
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.jsx +6 -0
- package/dist/components/Card/index.d.ts +1 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +10 -0
- package/dist/components/Checkbox/Checkbox.jsx +25 -0
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/Container/Container.d.ts +7 -0
- package/dist/components/Container/Container.jsx +5 -0
- package/dist/components/Container/index.d.ts +1 -0
- package/dist/components/Container/index.js +1 -0
- package/dist/components/DateInput/DateInput.d.ts +9 -0
- package/dist/components/DateInput/DateInput.jsx +22 -0
- package/dist/components/DateInput/index.d.ts +1 -0
- package/dist/components/DateInput/index.js +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +11 -0
- package/dist/components/DropdownMenu/DropdownMenu.jsx +22 -0
- package/dist/components/DropdownMenu/index.d.ts +1 -0
- package/dist/components/DropdownMenu/index.js +1 -0
- package/dist/components/FileInput/FileInput.d.ts +10 -0
- package/dist/components/FileInput/FileInput.jsx +18 -0
- package/dist/components/FileInput/index.d.ts +1 -0
- package/dist/components/FileInput/index.js +1 -0
- package/dist/components/Form/Form.d.ts +2 -0
- package/dist/components/Form/Form.jsx +3 -0
- package/dist/components/Form/index.d.ts +1 -0
- package/dist/components/Form/index.js +1 -0
- package/dist/components/Heading/Heading.d.ts +7 -0
- package/dist/components/Heading/Heading.jsx +29 -0
- package/dist/components/Heading/index.d.ts +1 -0
- package/dist/components/Heading/index.js +1 -0
- package/dist/components/Image/Image.d.ts +9 -0
- package/dist/components/Image/Image.jsx +15 -0
- package/dist/components/Image/index.d.ts +1 -0
- package/dist/components/Image/index.js +1 -0
- package/dist/components/Input/Input.d.ts +15 -0
- package/dist/components/Input/Input.jsx +86 -0
- package/dist/components/Input/index.d.ts +1 -0
- package/dist/components/Input/index.js +1 -0
- package/dist/components/Link/Link.d.ts +9 -0
- package/dist/components/Link/Link.jsx +13 -0
- package/dist/components/Link/index.d.ts +1 -0
- package/dist/components/Link/index.js +1 -0
- package/dist/components/List/List.d.ts +26 -0
- package/dist/components/List/List.jsx +120 -0
- package/dist/components/List/index.d.ts +1 -0
- package/dist/components/List/index.js +1 -0
- package/dist/components/Modal/Modal.d.ts +13 -0
- package/dist/components/Modal/Modal.jsx +51 -0
- package/dist/components/Modal/index.d.ts +1 -0
- package/dist/components/Modal/index.js +1 -0
- package/dist/components/Modal/modalContext.d.ts +7 -0
- package/dist/components/Modal/modalContext.js +2 -0
- package/dist/components/NumberInput/NumberInput.d.ts +13 -0
- package/dist/components/NumberInput/NumberInput.jsx +20 -0
- package/dist/components/NumberInput/index.d.ts +1 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/RelationPicker/RelationPicker.d.ts +11 -0
- package/dist/components/RelationPicker/RelationPicker.jsx +13 -0
- package/dist/components/RelationPicker/index.d.ts +1 -0
- package/dist/components/RelationPicker/index.js +1 -0
- package/dist/components/SearchInput/SearchInput.d.ts +9 -0
- package/dist/components/SearchInput/SearchInput.jsx +9 -0
- package/dist/components/SearchInput/index.d.ts +1 -0
- package/dist/components/SearchInput/index.js +1 -0
- package/dist/components/Select/Select.d.ts +15 -0
- package/dist/components/Select/Select.jsx +37 -0
- package/dist/components/Select/index.d.ts +1 -0
- package/dist/components/Select/index.js +1 -0
- package/dist/components/Switch/Switch.d.ts +11 -0
- package/dist/components/Switch/Switch.jsx +22 -0
- package/dist/components/Switch/index.d.ts +1 -0
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Tabs/Tabs.d.ts +3 -0
- package/dist/components/Tabs/Tabs.jsx +4 -0
- package/dist/components/Tabs/index.d.ts +1 -0
- package/dist/components/Tabs/index.js +1 -0
- package/dist/components/Tag/Tag.d.ts +10 -0
- package/dist/components/Tag/Tag.jsx +30 -0
- package/dist/components/Tag/index.d.ts +1 -0
- package/dist/components/Tag/index.js +1 -0
- package/dist/components/TagArea/TagArea.d.ts +11 -0
- package/dist/components/TagArea/TagArea.jsx +62 -0
- package/dist/components/TagArea/index.d.ts +1 -0
- package/dist/components/TagArea/index.js +1 -0
- package/dist/components/Text/Text.d.ts +9 -0
- package/dist/components/Text/Text.jsx +25 -0
- package/dist/components/Text/index.d.ts +1 -0
- package/dist/components/Text/index.js +1 -0
- package/dist/components/TextArea/TextArea.d.ts +14 -0
- package/dist/components/TextArea/TextArea.jsx +40 -0
- package/dist/components/TextArea/index.d.ts +1 -0
- package/dist/components/TextArea/index.js +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -0
- package/dist/components/Toast/Toast.jsx +36 -0
- package/dist/components/Toast/Toaster.d.ts +3 -0
- package/dist/components/Toast/Toaster.jsx +11 -0
- package/dist/components/Toast/index.d.ts +2 -0
- package/dist/components/Toast/index.js +2 -0
- package/dist/components/Tooltip/Tooltip.d.ts +8 -0
- package/dist/components/Tooltip/Tooltip.jsx +12 -0
- package/dist/components/Tooltip/index.d.ts +1 -0
- package/dist/components/Tooltip/index.js +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/methods/debounce.d.ts +1 -0
- package/dist/methods/debounce.js +7 -0
- package/package.json +41 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createSolidTable, flexRender, getCoreRowModel, getFilteredRowModel, } from "@tanstack/solid-table";
|
|
2
|
+
import { For, Show, createMemo, createSignal } from "solid-js";
|
|
3
|
+
import { createVirtualizer } from "@tanstack/solid-virtual";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
import Loader from "lucide-solid/icons/loader";
|
|
6
|
+
import Plus from "lucide-solid/icons/plus";
|
|
7
|
+
import { Button } from "../Button";
|
|
8
|
+
import { Input } from "../Input";
|
|
9
|
+
const containerClass = tv({
|
|
10
|
+
base: "overflow-y-auto relative flex-1",
|
|
11
|
+
});
|
|
12
|
+
export const DefaultRowRenderer = (props) => {
|
|
13
|
+
return (<div class="flex justify-between items-center cursor-pointer text-sm border-b border-[var(--color-light-muted)] dark:border-[var(--color-dark-muted)] py-2 hover:bg-[var(--color-light-muted)] dark:hover:bg-[var(--color-dark-muted)]" onClick={() => props.onClick(props.row.original)}>
|
|
14
|
+
<For each={props.row.getVisibleCells()}>
|
|
15
|
+
{(cell) => (<div class="flex-1 overflow-hidden truncate">
|
|
16
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
17
|
+
</div>)}
|
|
18
|
+
</For>
|
|
19
|
+
</div>);
|
|
20
|
+
};
|
|
21
|
+
export const List = (props) => {
|
|
22
|
+
const [globalFilter, setGlobalFilter] = createSignal();
|
|
23
|
+
let parentRef;
|
|
24
|
+
const table = createSolidTable({
|
|
25
|
+
get data() {
|
|
26
|
+
return props.data?.() || [];
|
|
27
|
+
},
|
|
28
|
+
columns: props.columns(),
|
|
29
|
+
getCoreRowModel: getCoreRowModel(),
|
|
30
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
31
|
+
state: {
|
|
32
|
+
get globalFilter() {
|
|
33
|
+
return globalFilter();
|
|
34
|
+
},
|
|
35
|
+
get columnFilters() {
|
|
36
|
+
return props.filters?.();
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const rowCount = createMemo(() => table.getRowModel().rows.length);
|
|
41
|
+
const dataExists = createMemo(() => props.data?.() || props.loading);
|
|
42
|
+
const rows = createMemo(() => table.getRowModel().rows);
|
|
43
|
+
const virtualizer = createMemo(() => createVirtualizer({
|
|
44
|
+
get count() {
|
|
45
|
+
return rows().length;
|
|
46
|
+
},
|
|
47
|
+
getScrollElement: () => {
|
|
48
|
+
return parentRef;
|
|
49
|
+
},
|
|
50
|
+
estimateSize: () => 41,
|
|
51
|
+
get getItemKey() {
|
|
52
|
+
return (index) => rows()[index].id;
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
55
|
+
const virtualRows = createMemo(() => virtualizer().getVirtualItems());
|
|
56
|
+
const totalSize = createMemo(() => virtualizer().getTotalSize());
|
|
57
|
+
const containerStyle = createMemo(() => containerClass({ class: props.containerClass }));
|
|
58
|
+
return (<div class="flex flex-col h-full max-h-[inherit]">
|
|
59
|
+
<Show when={dataExists()} fallback={props.loadingFallback || (<div class="fixed inset-0 z-100 flex items-center justify-center bg-[var(--color-dark-background)]/25 dark:bg-[var(--color-light-background)]/25">
|
|
60
|
+
<Loader class="w-9 h-9 animate-spin text-[var(--color-light-muted)] dark:text-[var(--color-dark-muted)]"/>
|
|
61
|
+
</div>)}>
|
|
62
|
+
<div class="sticky top-0 bg-charcoal-500/95 backdrop-blur-xs">
|
|
63
|
+
<Show when={props.search}>
|
|
64
|
+
<div class="flex items-center space-x-2 mt-3 mb-1">
|
|
65
|
+
<Show when={props.createFunc}>
|
|
66
|
+
<Button class="flex text-sm items-center pl-1 pr-2" variant="solid" appearance="success" onClick={props.createFunc}>
|
|
67
|
+
<Plus size={20}/>
|
|
68
|
+
New
|
|
69
|
+
</Button>
|
|
70
|
+
</Show>
|
|
71
|
+
|
|
72
|
+
<div class="w-full relative">
|
|
73
|
+
<Input class="w-full" value={globalFilter()} onChange={setGlobalFilter} inputProps={{ placeholder: props.searchPlaceholder ?? "Search", class: "p-1" }}/>
|
|
74
|
+
{props.loading && (<Loader size={16} class="absolute animate-spin top-1/2 transform -translate-y-1/2 right-2"/>)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</Show>
|
|
78
|
+
|
|
79
|
+
<Show when={props.headerActions}>{props.headerActions}</Show>
|
|
80
|
+
|
|
81
|
+
<Show when={props.showItemCount}>
|
|
82
|
+
<p class="text-xs italic mb-1">{rowCount()} items</p>
|
|
83
|
+
</Show>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<Show when={props.headers}>
|
|
87
|
+
<For each={table.getHeaderGroups()}>
|
|
88
|
+
{(headerGroup) => (<div class="flex flex-row w-full justify-between sticky top-0 bg-[var(--color-light-surface)]/95 dark:bg-[var(--color-dark-surface)]/95 text-[var(--color-text-light-primary)] dark:text-[var(--color-dark-primary)] backdrop-blur-sm p-2 z-10">
|
|
89
|
+
<For each={headerGroup.headers}>
|
|
90
|
+
{(header) => (<div class="text-left font-bold">
|
|
91
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
92
|
+
</div>)}
|
|
93
|
+
</For>
|
|
94
|
+
</div>)}
|
|
95
|
+
</For>
|
|
96
|
+
</Show>
|
|
97
|
+
|
|
98
|
+
<div ref={parentRef} class={containerStyle()}>
|
|
99
|
+
<Show when={rowCount() > 0} fallback={props.emptyState || <div class="text-center py-4">No results found.</div>}>
|
|
100
|
+
<div class="w-full" style={{
|
|
101
|
+
height: `${totalSize()}px`,
|
|
102
|
+
position: "relative",
|
|
103
|
+
}}>
|
|
104
|
+
<For each={virtualRows()}>
|
|
105
|
+
{(virtualRow) => {
|
|
106
|
+
const row = rows()[virtualRow.index];
|
|
107
|
+
return (<div data-index={virtualRow.index} ref={(el) => queueMicrotask(() => virtualizer().measureElement(el))} class="absolute w-full" style={{ transform: `translateY(${virtualRow.start}px)` }}>
|
|
108
|
+
<Show when={props.renderRow} fallback={<DefaultRowRenderer row={row} columns={props.columns} onClick={props.onRowClick}/>}>
|
|
109
|
+
{props.renderRow(row, props.onRowClick)}
|
|
110
|
+
</Show>
|
|
111
|
+
</div>);
|
|
112
|
+
}}
|
|
113
|
+
</For>
|
|
114
|
+
</div>
|
|
115
|
+
</Show>
|
|
116
|
+
</div>
|
|
117
|
+
</Show>
|
|
118
|
+
</div>);
|
|
119
|
+
};
|
|
120
|
+
export default List;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./List";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./List";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ParentComponent } from "solid-js";
|
|
2
|
+
interface Props {
|
|
3
|
+
setModalVisible?: (v: boolean) => void;
|
|
4
|
+
saveFunc?: () => Promise<void>;
|
|
5
|
+
zIndexClass?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const Modal: ParentComponent<Props>;
|
|
9
|
+
export default Modal;
|
|
10
|
+
export declare function useModalLoading(): {
|
|
11
|
+
loading: import("solid-js").Accessor<boolean>;
|
|
12
|
+
setLoading: import("solid-js").Setter<boolean>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createSignal, Show, useContext } from "solid-js";
|
|
2
|
+
import { Portal } from "solid-js/web";
|
|
3
|
+
import Loader from "lucide-solid/icons/loader";
|
|
4
|
+
import { Button } from "../Button";
|
|
5
|
+
import { ModalContext } from "./modalContext";
|
|
6
|
+
export const Modal = (props) => {
|
|
7
|
+
const [loading, setLoading] = createSignal(false);
|
|
8
|
+
const containerStyle = `${props.zIndexClass !== undefined ? props.zIndexClass : "z-50"} fixed inset-0 flex items-center justify-center bg-black/50`;
|
|
9
|
+
return (<Portal>
|
|
10
|
+
<Show when={loading()}>
|
|
11
|
+
<div class="fixed inset-0 z-100 flex items-center justify-center bg-gray-800/25">
|
|
12
|
+
<Loader class="w-9 h-9 animate-spin text-gray-700"/>
|
|
13
|
+
</div>
|
|
14
|
+
</Show>
|
|
15
|
+
<div class={containerStyle} onClick={() => props.setModalVisible?.(false)}>
|
|
16
|
+
<div class={`bg-charcoal-500 text-dark-slate-gray-900 rounded-xl shadow-lg p-4 md:p-6
|
|
17
|
+
w-full mx-3 sm:w-[50vw] lg:w-[35vw] flex flex-col`} onClick={(e) => e.stopPropagation()}>
|
|
18
|
+
<Show when={props.title}>
|
|
19
|
+
<h2 class="pb-2">{props.title}</h2>
|
|
20
|
+
</Show>
|
|
21
|
+
<div class="max-h-[55vh] overflow-y-hidden flex flex-col h-full">
|
|
22
|
+
<ModalContext.Provider value={{ loading, setLoading }}>{props.children}</ModalContext.Provider>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="w-full flex justify-end space-x-2">
|
|
25
|
+
<Button onClick={() => props.setModalVisible?.(false)} class="mt-3">
|
|
26
|
+
Cancel
|
|
27
|
+
</Button>
|
|
28
|
+
|
|
29
|
+
<Show when={props.saveFunc}>
|
|
30
|
+
<Button appearance="success" onClick={() => {
|
|
31
|
+
setLoading(true);
|
|
32
|
+
props.saveFunc()
|
|
33
|
+
.then(() => props.setModalVisible?.(false))
|
|
34
|
+
.finally(() => setLoading(false));
|
|
35
|
+
}} class="mt-3">
|
|
36
|
+
Save
|
|
37
|
+
</Button>
|
|
38
|
+
</Show>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</Portal>);
|
|
43
|
+
};
|
|
44
|
+
export default Modal;
|
|
45
|
+
export function useModalLoading() {
|
|
46
|
+
const context = useContext(ModalContext);
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error("useModalLoading must be used within a Modal");
|
|
49
|
+
}
|
|
50
|
+
return context;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Modal";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Modal";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Component, ValidComponent } from "solid-js";
|
|
2
|
+
import { type NumberFieldInputProps, type NumberFieldRootProps } from "@kobalte/core/number-field";
|
|
3
|
+
import type { PolymorphicProps } from "@kobalte/core";
|
|
4
|
+
type InputProps<T extends ValidComponent = "input"> = PolymorphicProps<T, NumberFieldInputProps<T>>;
|
|
5
|
+
interface ExtraProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
size?: "sm" | "md";
|
|
8
|
+
inputProps?: InputProps;
|
|
9
|
+
saveFunc?: (v: number) => Promise<any>;
|
|
10
|
+
}
|
|
11
|
+
type InputRootProps<T extends ValidComponent = "div"> = ExtraProps & PolymorphicProps<T, NumberFieldRootProps<T>>;
|
|
12
|
+
export declare const NumberInput: Component<InputRootProps>;
|
|
13
|
+
export default NumberInput;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createMemo, Show, splitProps } from "solid-js";
|
|
2
|
+
import { NumberField, } from "@kobalte/core/number-field";
|
|
3
|
+
import { debounce } from "../../methods/debounce";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
const inputRoot = tv({ base: "flex flex-row items-center space-x-1" });
|
|
6
|
+
const inputField = tv({ base: "input outline-none text-right my-0" });
|
|
7
|
+
export const NumberInput = (props) => {
|
|
8
|
+
const [local, others] = splitProps(props, ["label", "class", "inputProps", "saveFunc"]);
|
|
9
|
+
const debouncedSave = createMemo(() => (local.saveFunc ? debounce(local.saveFunc) : undefined));
|
|
10
|
+
const handleChange = (v) => {
|
|
11
|
+
debouncedSave()?.(Number(v));
|
|
12
|
+
};
|
|
13
|
+
return (<NumberField {...others} onChange={handleChange} class={inputRoot({ class: local.class })}>
|
|
14
|
+
<Show when={local.label}>
|
|
15
|
+
<NumberField.Label>{local.label}</NumberField.Label>
|
|
16
|
+
</Show>
|
|
17
|
+
<NumberField.Input {...local.inputProps} class={inputField({ class: local.inputProps?.class ?? "" })}/>
|
|
18
|
+
</NumberField>);
|
|
19
|
+
};
|
|
20
|
+
export default NumberInput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./NumberInput";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./NumberInput";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface RelationPickerProps<T extends TagRecord = TagRecord> {
|
|
2
|
+
relations: T[];
|
|
3
|
+
setRelations: (relations: T[]) => void;
|
|
4
|
+
suggestions?: T[];
|
|
5
|
+
onCreateRelation: (name: string) => Promise<T>;
|
|
6
|
+
onDeleteRelation: (relation: T) => Promise<void>;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const RelationPicker: <T extends TagRecord = TagRecord>(props: RelationPickerProps<T>) => import("solid-js").JSX.Element;
|
|
11
|
+
export default RelationPicker;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Show } from "solid-js";
|
|
2
|
+
import TagArea from "../TagArea/TagArea";
|
|
3
|
+
export const RelationPicker = (props) => {
|
|
4
|
+
return (<div class="flex flex-col gap-1">
|
|
5
|
+
<Show when={props.label}>
|
|
6
|
+
<span class="text-xs font-medium text-(--color-text-light-secondary) dark:text-(--color-text-dark-secondary) mb-1">
|
|
7
|
+
{props.label}
|
|
8
|
+
</span>
|
|
9
|
+
</Show>
|
|
10
|
+
<TagArea tags={props.relations} setTags={props.setRelations} suggestions={props.suggestions} onCreateTag={props.onCreateRelation} onDeleteTag={props.onDeleteRelation} placeholder={props.placeholder || "Add relation..."}/>
|
|
11
|
+
</div>);
|
|
12
|
+
};
|
|
13
|
+
export default RelationPicker;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./RelationPicker";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./RelationPicker";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TextField } from "@kobalte/core/text-field";
|
|
2
|
+
import { tv } from "tailwind-variants";
|
|
3
|
+
const search = tv({
|
|
4
|
+
base: "w-full rounded px-2 py-1 border border-[var(--color-light-muted)] dark:border-[var(--color-dark-muted)] bg-[var(--color-light-surface)] dark:bg-[var(--color-dark-surface)]",
|
|
5
|
+
});
|
|
6
|
+
export const SearchInput = (props) => (<TextField class={search({ class: props.class })} value={props.value} onChange={props.onChange}>
|
|
7
|
+
<TextField.Input placeholder={props.placeholder || "Search..."}/>
|
|
8
|
+
</TextField>);
|
|
9
|
+
export default SearchInput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SearchInput";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SearchInput";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Component } from "solid-js";
|
|
2
|
+
interface Option {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
interface SelectProps {
|
|
7
|
+
label?: string;
|
|
8
|
+
options: Option[];
|
|
9
|
+
value: string;
|
|
10
|
+
onChange: (value: string | null) => void;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const Select: Component<SelectProps>;
|
|
15
|
+
export default Select;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Select as KobalteSelect } from "@kobalte/core/select";
|
|
2
|
+
import Check from "lucide-solid/icons/check";
|
|
3
|
+
import UpDown from "lucide-solid/icons/chevrons-up-down";
|
|
4
|
+
export const Select = (props) => {
|
|
5
|
+
return (<div class={props.class}>
|
|
6
|
+
{props.label && (<label class="block mb-1 text-xs font-medium text-[var(--color-text-light-secondary)] dark:text-[var(--color-text-dark-secondary)]">
|
|
7
|
+
{props.label}
|
|
8
|
+
</label>)}
|
|
9
|
+
<KobalteSelect multiple={false} value={props.value} onChange={props.onChange} options={props.options.map((o) => o.value)} placeholder={props.placeholder} itemComponent={(itemProps) => {
|
|
10
|
+
const option = props.options.find((o) => o.value === itemProps.item.rawValue);
|
|
11
|
+
return (<KobalteSelect.Item item={itemProps.item} class="flex flex-row space-x-1 px-2 py-1 hover:bg-[var(--color-light-muted)] dark:hover:bg-[var(--color-dark-muted)] rounded cursor-pointer">
|
|
12
|
+
<KobalteSelect.ItemLabel>{option?.label ?? itemProps.item.textValue}</KobalteSelect.ItemLabel>
|
|
13
|
+
<KobalteSelect.ItemIndicator>
|
|
14
|
+
<Check size={16}/>
|
|
15
|
+
</KobalteSelect.ItemIndicator>
|
|
16
|
+
</KobalteSelect.Item>);
|
|
17
|
+
}}>
|
|
18
|
+
<KobalteSelect.Trigger class="rounded border px-3 py-1 bg-[var(--color-light-surface)] dark:bg-[var(--color-dark-surface)] flex flex-row items-center justify-between w-full text-[var(--color-text-light-primary)] dark:text-[var(--color-text-dark-primary)]">
|
|
19
|
+
<KobalteSelect.Value>
|
|
20
|
+
{(state) => {
|
|
21
|
+
const selected = props.options.find((o) => o.value === state.selectedOption());
|
|
22
|
+
return selected ? selected.label : props.placeholder || "Select...";
|
|
23
|
+
}}
|
|
24
|
+
</KobalteSelect.Value>
|
|
25
|
+
<KobalteSelect.Icon>
|
|
26
|
+
<UpDown size={16}/>
|
|
27
|
+
</KobalteSelect.Icon>
|
|
28
|
+
</KobalteSelect.Trigger>
|
|
29
|
+
<KobalteSelect.Portal>
|
|
30
|
+
<KobalteSelect.Content class="rounded border bg-[var(--color-light-surface)] dark:bg-[var(--color-dark-surface)] text-[var(--color-text-light-primary)] dark:text-[var(--color-text-dark-primary)] shadow-lg mt-1">
|
|
31
|
+
<KobalteSelect.Listbox class="max-h-50 overflow-y-auto"/>
|
|
32
|
+
</KobalteSelect.Content>
|
|
33
|
+
</KobalteSelect.Portal>
|
|
34
|
+
</KobalteSelect>
|
|
35
|
+
</div>);
|
|
36
|
+
};
|
|
37
|
+
export default Select;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Select";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Select";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SwitchRootProps } from "@kobalte/core/switch";
|
|
2
|
+
import { Component, ValidComponent } from "solid-js";
|
|
3
|
+
import { PolymorphicProps } from "@kobalte/core";
|
|
4
|
+
type SwitchProps<T extends ValidComponent = "div"> = PolymorphicProps<T, SwitchRootProps<T>>;
|
|
5
|
+
interface Props extends SwitchProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
size?: "sm" | "md";
|
|
8
|
+
saveFunc?: (v: boolean) => Promise<any>;
|
|
9
|
+
}
|
|
10
|
+
export declare const Switch: Component<Props>;
|
|
11
|
+
export default Switch;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Switch as KSwitch } from "@kobalte/core/switch";
|
|
2
|
+
import { createMemo, Show } from "solid-js";
|
|
3
|
+
import { debounce } from "../../methods/debounce";
|
|
4
|
+
export const Switch = (props) => {
|
|
5
|
+
const debouncedSave = createMemo(() => (props.saveFunc ? debounce(props.saveFunc) : undefined));
|
|
6
|
+
const handleChange = (v) => {
|
|
7
|
+
props.onChange?.(v);
|
|
8
|
+
debouncedSave()?.(v);
|
|
9
|
+
};
|
|
10
|
+
return (<KSwitch class={`flex flex-row justify-between items-center ${props.class ?? ""}`} checked={props.checked} onChange={handleChange}>
|
|
11
|
+
<Show when={props.label}>
|
|
12
|
+
<KSwitch.Label>{props.label}</KSwitch.Label>
|
|
13
|
+
</Show>
|
|
14
|
+
<KSwitch.Input />
|
|
15
|
+
<KSwitch.Control class={`flex items-center w-10 h-6 bg-black rounded-full border-1 border-black
|
|
16
|
+
data-[checked]:bg-white data-[checked]:border-white transition-all`}>
|
|
17
|
+
<KSwitch.Thumb class={`h-5 w-5 rounded-full bg-white transition-transform
|
|
18
|
+
data-[checked]:[transform:translateX(calc(100%-2px))]`}/>
|
|
19
|
+
</KSwitch.Control>
|
|
20
|
+
</KSwitch>);
|
|
21
|
+
};
|
|
22
|
+
export default Switch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Switch";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Switch";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tabs";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tabs";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createMemo } from "solid-js";
|
|
2
|
+
import { Button as KobalteButton } from "@kobalte/core/button";
|
|
3
|
+
import CloseIcon from "lucide-solid/icons/x";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
const tag = tv({
|
|
6
|
+
base: "flex items-center rounded-full select-none border-1 font-bold",
|
|
7
|
+
variants: {
|
|
8
|
+
size: {
|
|
9
|
+
m: "text-sm px-2 py-1",
|
|
10
|
+
s: "text-sm pl-1.5 pr-1 py-0.5",
|
|
11
|
+
xs: "text-xs px-1",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
size: "m",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
export const Tag = (props) => {
|
|
19
|
+
const classes = createMemo(() => tag({
|
|
20
|
+
size: props.size,
|
|
21
|
+
class: props.class,
|
|
22
|
+
}));
|
|
23
|
+
return (<div style={{ "background-color": `${props.colorHex}30`, color: `${props.colorHex}` }} class={classes()}>
|
|
24
|
+
<span>{props.title}</span>
|
|
25
|
+
<KobalteButton onClick={props.onClick} class="pl-1 flex items-center justify-center">
|
|
26
|
+
<CloseIcon size={14}/>
|
|
27
|
+
</KobalteButton>
|
|
28
|
+
</div>);
|
|
29
|
+
};
|
|
30
|
+
export default Tag;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tag";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tag";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JSX } from "solid-js";
|
|
2
|
+
interface TagAreaProps<T extends TagRecord = TagRecord> {
|
|
3
|
+
tags: T[];
|
|
4
|
+
setTags: (tags: T[]) => void;
|
|
5
|
+
onCreateTag: (name: string) => Promise<T>;
|
|
6
|
+
onDeleteTag: (tag: T) => Promise<void>;
|
|
7
|
+
suggestions?: T[];
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const TagArea: <T extends TagRecord = TagRecord>(props: TagAreaProps<T>) => JSX.Element;
|
|
11
|
+
export default TagArea;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createSignal, For, Show, createMemo } from "solid-js";
|
|
2
|
+
import { Input } from "../Input";
|
|
3
|
+
import Tag from "../Tag/Tag";
|
|
4
|
+
export const TagArea = (props) => {
|
|
5
|
+
const [tagInput, setTagInput] = createSignal("");
|
|
6
|
+
const [showSuggestions, setShowSuggestions] = createSignal(false);
|
|
7
|
+
const filteredSuggestions = createMemo(() => (props.suggestions || []).filter((s) => s.name.toLowerCase().includes(tagInput().toLowerCase()) && !props.tags.some((t) => t.id === s.id)));
|
|
8
|
+
const handleTagInput = async (e) => {
|
|
9
|
+
if (e.key === "Enter" && tagInput().trim()) {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
const newTagName = tagInput().trim();
|
|
12
|
+
if (!props.tags.map((t) => t.name).includes(newTagName)) {
|
|
13
|
+
let newTag = undefined;
|
|
14
|
+
newTag = await props.onCreateTag(newTagName);
|
|
15
|
+
if (newTag)
|
|
16
|
+
props.setTags([...props.tags, newTag]);
|
|
17
|
+
}
|
|
18
|
+
setTagInput("");
|
|
19
|
+
setShowSuggestions(false);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
setShowSuggestions(true);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const handleSuggestionClick = (tag) => {
|
|
26
|
+
props.setTags([...props.tags, tag]);
|
|
27
|
+
setTagInput("");
|
|
28
|
+
setShowSuggestions(false);
|
|
29
|
+
};
|
|
30
|
+
const deleteTag = async (t) => {
|
|
31
|
+
await props.onDeleteTag(t);
|
|
32
|
+
props.setTags((props.tags || []).filter((tag) => tag.id !== t.id));
|
|
33
|
+
};
|
|
34
|
+
return (<div class="rounded-md p-2 flex flex-col bg-[var(--color-light-surface)] dark:bg-[var(--color-dark-surface)]">
|
|
35
|
+
<div class="flex flex-wrap gap-2">
|
|
36
|
+
<For each={props.tags || []}>
|
|
37
|
+
{(t) => (<Tag title={t.name || ""} colorHex={t.colorHex || "#6b7280"} onClick={() => deleteTag(t)}/>)}
|
|
38
|
+
</For>
|
|
39
|
+
<div class="relative flex-1 min-w-[120px]">
|
|
40
|
+
<Input label="" value={tagInput()} onChange={(v) => {
|
|
41
|
+
setTagInput(v);
|
|
42
|
+
setShowSuggestions(true);
|
|
43
|
+
}} inputProps={{
|
|
44
|
+
onKeyDown: handleTagInput,
|
|
45
|
+
placeholder: props.placeholder || "Add tags (press Enter)",
|
|
46
|
+
onFocus: () => setShowSuggestions(true),
|
|
47
|
+
onBlur: () => setTimeout(() => setShowSuggestions(false), 100),
|
|
48
|
+
}} class="w-full"/>
|
|
49
|
+
<Show when={showSuggestions() && filteredSuggestions().length > 0}>
|
|
50
|
+
<div class="absolute z-10 mt-1 w-full bg-[var(--color-light-surface)] dark:bg-[var(--color-dark-surface)] border border-[var(--color-light-muted)] dark:border-[var(--color-dark-muted)] rounded shadow-lg max-h-40 overflow-auto">
|
|
51
|
+
<For each={filteredSuggestions()}>
|
|
52
|
+
{(s) => (<div class="px-3 py-2 cursor-pointer hover:bg-[var(--color-light-muted)] dark:hover:bg-[var(--color-dark-muted)]" onMouseDown={() => handleSuggestionClick(s)}>
|
|
53
|
+
{s.name}
|
|
54
|
+
</div>)}
|
|
55
|
+
</For>
|
|
56
|
+
</div>
|
|
57
|
+
</Show>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>);
|
|
61
|
+
};
|
|
62
|
+
export default TagArea;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./TagArea";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./TagArea";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Component, JSX } from "solid-js";
|
|
2
|
+
interface TextProps {
|
|
3
|
+
children: JSX.Element;
|
|
4
|
+
size?: "sm" | "md" | "lg";
|
|
5
|
+
color?: "primary" | "secondary" | "error" | "success" | "warning" | "muted";
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const Text: Component<TextProps>;
|
|
9
|
+
export default Text;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tv } from "tailwind-variants";
|
|
2
|
+
const text = tv({
|
|
3
|
+
base: "font-normal",
|
|
4
|
+
variants: {
|
|
5
|
+
size: {
|
|
6
|
+
sm: "text-sm",
|
|
7
|
+
md: "text-base",
|
|
8
|
+
lg: "text-lg",
|
|
9
|
+
},
|
|
10
|
+
color: {
|
|
11
|
+
primary: "text-[var(--color-text-light-primary)] dark:text-[var(--color-text-dark-primary)]",
|
|
12
|
+
secondary: "text-[var(--color-text-light-secondary)] dark:text-[var(--color-text-dark-secondary)]",
|
|
13
|
+
error: "text-[var(--color-light-error)] dark:text-[var(--color-dark-error)]",
|
|
14
|
+
success: "text-[var(--color-light-success)] dark:text-[var(--color-dark-success)]",
|
|
15
|
+
warning: "text-[var(--color-light-warning)] dark:text-[var(--color-dark-warning)]",
|
|
16
|
+
muted: "text-[var(--color-light-muted)] dark:text-[var(--color-dark-muted)]",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
size: "md",
|
|
21
|
+
color: "primary",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
export const Text = (props) => (<span class={text({ size: props.size, color: props.color, class: props.class })}>{props.children}</span>);
|
|
25
|
+
export default Text;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Text";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Text";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Component, ValidComponent } from "solid-js";
|
|
2
|
+
import { type TextFieldInputProps, type TextFieldRootProps } from "@kobalte/core/text-field";
|
|
3
|
+
import type { PolymorphicProps } from "@kobalte/core";
|
|
4
|
+
type InputProps<T extends ValidComponent = "textarea"> = PolymorphicProps<T, TextFieldInputProps<T>>;
|
|
5
|
+
interface ExtraProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
saveFunc?: (v: string) => Promise<any>;
|
|
8
|
+
inputProps?: InputProps;
|
|
9
|
+
variant?: "bordered" | "none";
|
|
10
|
+
size?: "sm" | "md";
|
|
11
|
+
}
|
|
12
|
+
type InputRootProps<T extends ValidComponent = "div"> = ExtraProps & PolymorphicProps<T, TextFieldRootProps<T>>;
|
|
13
|
+
export declare const TextArea: Component<InputRootProps>;
|
|
14
|
+
export default TextArea;
|