@solidpb/ui-kit 0.2.0 → 0.4.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/dist/components/Container/Container.d.ts +0 -1
- package/dist/components/Container/Container.jsx +2 -8
- package/dist/components/FileInput/FileInput.jsx +16 -1
- package/dist/components/FilterBar/FilterBar.d.ts +0 -2
- package/dist/components/{Form/Form.d.ts → Form.d.ts} +15 -10
- package/dist/components/{Form/Form.jsx → Form.jsx} +42 -18
- package/dist/components/Image/Image.jsx +21 -3
- package/dist/components/Kanban/Kanban.d.ts +1 -1
- package/dist/components/Kanban/Kanban.jsx +7 -5
- package/dist/components/Kanban/KanbanColumn.d.ts +1 -1
- package/dist/components/Kanban/KanbanColumn.jsx +23 -28
- package/dist/components/Navbar/Navbar.d.ts +6 -2
- package/dist/components/Navbar/Navbar.jsx +3 -1
- package/dist/components/RelationPicker.d.ts +23 -0
- package/dist/components/RelationPicker.jsx +144 -0
- package/dist/components/Select/Select.jsx +13 -9
- package/dist/components/Table/Table.d.ts +3 -3
- package/dist/components/Table/Table.jsx +3 -3
- package/dist/components/ThemeSwitch/ThemeSwitch.d.ts +10 -1
- package/dist/components/ThemeSwitch/ThemeSwitch.jsx +34 -59
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/dist/components/Form/index.d.ts +0 -1
- package/dist/components/Form/index.js +0 -1
- /package/dist/components/{Form/formContext.d.ts → formContext.d.ts} +0 -0
- /package/dist/components/{Form/formContext.js → formContext.js} +0 -0
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { tv } from "tailwind-variants";
|
|
2
2
|
const container = tv({
|
|
3
|
-
base: "flex-1 bg-base-100
|
|
4
|
-
variants: {
|
|
5
|
-
noPadding: {
|
|
6
|
-
true: "",
|
|
7
|
-
false: "py-4 px-[2vw]",
|
|
8
|
-
},
|
|
9
|
-
},
|
|
3
|
+
base: "flex-1 bg-base-100 min-h-[calc(100vh-4rem)] py-4 px-[2vw]",
|
|
10
4
|
});
|
|
11
5
|
export const Container = (props) => {
|
|
12
|
-
const classes = container({
|
|
6
|
+
const classes = container({ class: props.class });
|
|
13
7
|
return <div class={classes}>{props.children}</div>;
|
|
14
8
|
};
|
|
15
9
|
export default Container;
|
|
@@ -28,6 +28,21 @@ const input = tv({
|
|
|
28
28
|
size: "sm",
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
|
+
const label = tv({
|
|
32
|
+
base: "label text-xs",
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
xs: "text-xs",
|
|
36
|
+
sm: "text-xs",
|
|
37
|
+
md: "text-xs",
|
|
38
|
+
lg: "text-sm",
|
|
39
|
+
xl: "text-sm",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
size: "sm",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
31
46
|
export const FileInput = (props) => {
|
|
32
47
|
const [local, others] = splitProps(props, ["label", "class", "onChange", "saveFunc"]);
|
|
33
48
|
const handleChange = (files) => {
|
|
@@ -36,7 +51,7 @@ export const FileInput = (props) => {
|
|
|
36
51
|
};
|
|
37
52
|
return (<label class="flex flex-col gap-1 w-fit">
|
|
38
53
|
<Show when={local.label}>
|
|
39
|
-
<span class=
|
|
54
|
+
<span class={label({ size: props.size })}>{local.label}</span>
|
|
40
55
|
</Show>
|
|
41
56
|
<input {...others} type="file" class={input({ class: local.class })} accept={props.accept} multiple={props.multiple} onChange={(e) => handleChange(e.currentTarget.files)}/>
|
|
42
57
|
</label>);
|
|
@@ -24,12 +24,10 @@ export interface FilterField<T> {
|
|
|
24
24
|
name: keyof T;
|
|
25
25
|
label: string;
|
|
26
26
|
type: FieldType;
|
|
27
|
-
operators?: FilterOperator[];
|
|
28
27
|
options?: {
|
|
29
28
|
label: string;
|
|
30
29
|
value: string;
|
|
31
30
|
}[];
|
|
32
|
-
searchable?: boolean;
|
|
33
31
|
}
|
|
34
32
|
export interface SortOption<T> {
|
|
35
33
|
field: keyof T;
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { JSXElement } from "solid-js";
|
|
2
|
-
import { type SwitchProps } from "
|
|
3
|
-
import { type SelectProps } from "
|
|
4
|
-
import { type InputRootProps } from "
|
|
5
|
-
import { type TextAreaRootProps } from "
|
|
6
|
-
import { type CheckboxProps } from "
|
|
7
|
-
import { type NumberInputRootProps } from "
|
|
8
|
-
import { type SliderProps } from "
|
|
9
|
-
import { type ImageProps } from "
|
|
10
|
-
import { type FileInputProps } from "
|
|
2
|
+
import { type SwitchProps } from "./Switch";
|
|
3
|
+
import { type SelectProps } from "./Select";
|
|
4
|
+
import { type InputRootProps } from "./Input";
|
|
5
|
+
import { type TextAreaRootProps } from "./TextArea";
|
|
6
|
+
import { type CheckboxProps } from "./Checkbox";
|
|
7
|
+
import { type NumberInputRootProps } from "./NumberInput";
|
|
8
|
+
import { type SliderProps } from "./Slider";
|
|
9
|
+
import { type ImageProps } from "./Image";
|
|
10
|
+
import { type FileInputProps } from "./FileInput";
|
|
11
|
+
import { RelationPickerProps } from "./RelationPicker";
|
|
11
12
|
export interface FormProps<T> {
|
|
12
|
-
data: T
|
|
13
|
+
data: Partial<T>;
|
|
13
14
|
title?: string;
|
|
14
15
|
onSave?: (values: Partial<T>) => Promise<void>;
|
|
15
16
|
onCancel?: () => void;
|
|
16
17
|
children: JSXElement;
|
|
18
|
+
class?: string;
|
|
17
19
|
}
|
|
18
20
|
type BaseFieldProps<T> = {
|
|
19
21
|
field: keyof T;
|
|
@@ -32,5 +34,8 @@ export declare function createForm<T>(): {
|
|
|
32
34
|
FileField: (props: FileInputProps & BaseFieldProps<T>) => import("solid-js").JSX.Element;
|
|
33
35
|
ImageField: (props: ImageProps & BaseFieldProps<T>) => import("solid-js").JSX.Element;
|
|
34
36
|
SliderField: (props: SliderProps & BaseFieldProps<T>) => import("solid-js").JSX.Element;
|
|
37
|
+
RelationField: <K extends {
|
|
38
|
+
id: string;
|
|
39
|
+
}>(props: RelationPickerProps<K> & BaseFieldProps<T>) => import("solid-js").JSX.Element;
|
|
35
40
|
};
|
|
36
41
|
export {};
|
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
import { splitProps } from "solid-js";
|
|
2
2
|
import { createStore } from "solid-js/store";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
3
4
|
import { InternalFormContext, useInternalFormContext } from "./formContext";
|
|
4
|
-
import { Switch } from "
|
|
5
|
-
import { Select } from "
|
|
6
|
-
import { Input } from "
|
|
7
|
-
import { TextArea } from "
|
|
8
|
-
import { Checkbox } from "
|
|
9
|
-
import { NumberInput } from "
|
|
10
|
-
import { Slider } from "
|
|
11
|
-
import { Image } from "
|
|
12
|
-
import { Button } from "
|
|
13
|
-
import { FileInput } from "
|
|
5
|
+
import { Switch } from "./Switch";
|
|
6
|
+
import { Select } from "./Select";
|
|
7
|
+
import { Input } from "./Input";
|
|
8
|
+
import { TextArea } from "./TextArea";
|
|
9
|
+
import { Checkbox } from "./Checkbox";
|
|
10
|
+
import { NumberInput } from "./NumberInput";
|
|
11
|
+
import { Slider } from "./Slider";
|
|
12
|
+
import { Image } from "./Image";
|
|
13
|
+
import { Button } from "./Button";
|
|
14
|
+
import { FileInput } from "./FileInput";
|
|
15
|
+
import RelationPicker from "./RelationPicker";
|
|
16
|
+
const formClass = tv({
|
|
17
|
+
base: "space-y-4 space-x-4",
|
|
18
|
+
});
|
|
14
19
|
export function createForm() {
|
|
15
20
|
const Form = (props) => {
|
|
16
21
|
const [values, setValues] = createStore({ ...props.data });
|
|
17
22
|
const setValue = (key, value) => {
|
|
23
|
+
console.log("Setting value", key, value);
|
|
18
24
|
setValues(key, value);
|
|
19
25
|
};
|
|
20
26
|
const getValue = (key) => {
|
|
21
27
|
return values[key];
|
|
22
28
|
};
|
|
23
|
-
const contextValue = { setValue, getValue
|
|
29
|
+
const contextValue = { setValue, getValue };
|
|
24
30
|
const handleSubmit = (e) => {
|
|
25
31
|
e.preventDefault();
|
|
26
|
-
props.onSave?.(values);
|
|
32
|
+
props.onSave?.(JSON.parse(JSON.stringify(values))); // deep clone to avoid issues with reactive proxies
|
|
27
33
|
};
|
|
28
34
|
return (<InternalFormContext.Provider value={contextValue}>
|
|
29
|
-
<form onSubmit={handleSubmit} class=
|
|
35
|
+
<form onSubmit={handleSubmit} class={formClass({ class: props.class })}>
|
|
30
36
|
{props.title && <h2 class="text-lg font-semibold">{props.title}</h2>}
|
|
31
37
|
|
|
32
38
|
{props.children}
|
|
33
39
|
|
|
34
40
|
<div class="flex justify-end gap-2">
|
|
35
|
-
{props.onCancel && (<Button appearance="neutral" onClick={props.onCancel}
|
|
41
|
+
{props.onCancel && (<Button appearance="neutral" onClick={props.onCancel}>
|
|
36
42
|
Cancel
|
|
37
43
|
</Button>)}
|
|
38
|
-
{props.onSave && (<Button appearance="success" type="submit"
|
|
44
|
+
{props.onSave && (<Button appearance="success" type="submit">
|
|
39
45
|
Save
|
|
40
46
|
</Button>)}
|
|
41
47
|
</div>
|
|
@@ -76,15 +82,32 @@ export function createForm() {
|
|
|
76
82
|
};
|
|
77
83
|
const ImageField = (props) => {
|
|
78
84
|
const form = useInternalFormContext();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
const [local, others] = splitProps(props, ["onChange"]);
|
|
86
|
+
// have to set src manually, when using with pocketbase, the value will be a URL string,
|
|
87
|
+
// but when uploading a new file it will be a File object, so we need to handle both cases
|
|
88
|
+
return (<Image editable {...others} onChange={(file) => {
|
|
89
|
+
form.setValue(props.field, file);
|
|
90
|
+
local.onChange?.(file);
|
|
82
91
|
}}/>);
|
|
83
92
|
};
|
|
84
93
|
const SliderField = (props) => {
|
|
85
94
|
const form = useInternalFormContext();
|
|
86
95
|
return (<Slider {...props} value={form.getValue(props.field)} onChange={(v) => form.setValue(props.field, v)}/>);
|
|
87
96
|
};
|
|
97
|
+
const RelationField = (props) => {
|
|
98
|
+
const form = useInternalFormContext();
|
|
99
|
+
const [local, others] = splitProps(props, ["onChange"]);
|
|
100
|
+
const handleChange = (val) => {
|
|
101
|
+
if (props.multi) {
|
|
102
|
+
form.setValue(props.field, (Array.isArray(val) ? val.map((v) => v.id) : []));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
form.setValue(props.field, (val?.id || null));
|
|
106
|
+
}
|
|
107
|
+
local.onChange?.(val);
|
|
108
|
+
};
|
|
109
|
+
return <RelationPicker {...others} onChange={handleChange}/>;
|
|
110
|
+
};
|
|
88
111
|
Form.TextField = TextField;
|
|
89
112
|
Form.NumberField = NumberField;
|
|
90
113
|
Form.CheckboxField = CheckboxField;
|
|
@@ -94,5 +117,6 @@ export function createForm() {
|
|
|
94
117
|
Form.FileField = FileField;
|
|
95
118
|
Form.ImageField = ImageField;
|
|
96
119
|
Form.SliderField = SliderField;
|
|
120
|
+
Form.RelationField = RelationField;
|
|
97
121
|
return Form;
|
|
98
122
|
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { Show, createSignal } from "solid-js";
|
|
2
2
|
import { tv } from "tailwind-variants";
|
|
3
3
|
import Pencil from "lucide-solid/icons/pencil";
|
|
4
|
+
import ImageIcon from "lucide-solid/icons/image";
|
|
4
5
|
import { Button } from "../Button";
|
|
5
6
|
const image = tv({
|
|
6
|
-
base: "rounded
|
|
7
|
+
base: "rounded-sm object-cover",
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
xs: "w-16 h-16",
|
|
11
|
+
sm: "w-24 h-24",
|
|
12
|
+
md: "w-32 h-32",
|
|
13
|
+
lg: "w-48 h-48",
|
|
14
|
+
xl: "w-64 h-64",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const placeholder = tv({
|
|
19
|
+
base: "rounded-sm flex items-center justify-center bg-base-200",
|
|
7
20
|
variants: {
|
|
8
21
|
size: {
|
|
9
22
|
xs: "w-16 h-16",
|
|
@@ -45,14 +58,19 @@ export const Image = (props) => {
|
|
|
45
58
|
};
|
|
46
59
|
// Destructure onChange and editable so they are not passed to <img>
|
|
47
60
|
const { onChange, editable, ...imgProps } = props;
|
|
61
|
+
const currentSrc = () => preview() || props.src;
|
|
48
62
|
return (<label class="flex flex-col gap-1 w-fit">
|
|
49
63
|
<Show when={props.label}>
|
|
50
64
|
<span class={label({ size: props.size })}>{props.label}</span>
|
|
51
65
|
</Show>
|
|
52
66
|
<div class="relative inline-block group w-fit">
|
|
53
|
-
<
|
|
67
|
+
<Show when={currentSrc()} fallback={<div class={placeholder({ size: props.size, class: props.class })}>
|
|
68
|
+
<ImageIcon class="w-1/2 h-1/2 text-base-300"/>
|
|
69
|
+
</div>}>
|
|
70
|
+
<img {...imgProps} src={currentSrc()} alt={props.alt} class={image({ size: props.size, class: props.class })}/>
|
|
71
|
+
</Show>
|
|
54
72
|
<Show when={editable}>
|
|
55
|
-
<div class="absolute inset-0 flex items-center justify-center bg-black/10 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
|
73
|
+
<div class="absolute inset-0 flex items-center justify-center bg-black/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 rounded-sm">
|
|
56
74
|
<Button size="sm" modifier="square" variant="ghost" onClick={handleEditClick}>
|
|
57
75
|
<Pencil class="w-4 h-4"/>
|
|
58
76
|
</Button>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, createMemo, createSignal, For, onCleanup } from "solid-js";
|
|
1
|
+
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";
|
|
2
2
|
import { extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
|
|
3
3
|
import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
4
4
|
import { tv } from "tailwind-variants";
|
|
@@ -11,7 +11,7 @@ export const Kanban = (props) => {
|
|
|
11
11
|
const posKey = props.statePositionKey;
|
|
12
12
|
if (!posKey)
|
|
13
13
|
return props.columns;
|
|
14
|
-
return props.columns
|
|
14
|
+
return props.columns?.toSorted((a, b) => (Number(a[posKey]) ?? 0) - (Number(b[posKey]) ?? 0));
|
|
15
15
|
});
|
|
16
16
|
const colDragEnabled = () => !!props.statePositionKey;
|
|
17
17
|
const [flashedColId, setFlashedColId] = createSignal(null);
|
|
@@ -58,9 +58,11 @@ export const Kanban = (props) => {
|
|
|
58
58
|
onCleanup(dispose);
|
|
59
59
|
});
|
|
60
60
|
return (<div class={container({ class: props.containerClass })}>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
<Show when={sortedColumns()?.length} fallback={<KanbanColumn dragEnabled={colDragEnabled} cardClass={props.cardClass} items={props.items} class={props.columnClass} onCardClick={props.onCardClick} renderItem={props.renderItem} onCreateItem={props.onCreateItem} onReorderCard={props.onReorderCard} onCollapse={props.onCollapseColumn} flashSignal={() => flashedColId()} itemPositionKey={props.itemPositionKey} itemStateKey={props.itemStateKey}/>}>
|
|
62
|
+
<For each={sortedColumns()}>
|
|
63
|
+
{(col) => (<KanbanColumn col={col} dragEnabled={colDragEnabled} cardClass={props.cardClass} items={props.items} class={props.columnClass} onCardClick={props.onCardClick} renderItem={props.renderItem} onCreateItem={props.onCreateItem} onReorderCard={props.onReorderCard} onCollapse={props.onCollapseColumn} flashSignal={() => flashedColId()} itemPositionKey={props.itemPositionKey} itemStateKey={props.itemStateKey}/>)}
|
|
64
|
+
</For>
|
|
65
|
+
</Show>
|
|
64
66
|
</div>);
|
|
65
67
|
};
|
|
66
68
|
export default Kanban;
|
|
@@ -12,7 +12,7 @@ import { Button } from "../Button";
|
|
|
12
12
|
import { KanbanCard } from "./KanbanCard";
|
|
13
13
|
import { Input } from "../Input";
|
|
14
14
|
const column = tv({
|
|
15
|
-
base: "kanban-column flex flex-col gap-1
|
|
15
|
+
base: "kanban-column flex flex-col gap-1 bg-base-300 p-1.5 rounded-md transition-[width] text-nowrap",
|
|
16
16
|
variants: {
|
|
17
17
|
folded: {
|
|
18
18
|
true: "w-9",
|
|
@@ -35,9 +35,6 @@ const columnHeader = tv({
|
|
|
35
35
|
folded: false,
|
|
36
36
|
},
|
|
37
37
|
});
|
|
38
|
-
const columnContent = tv({
|
|
39
|
-
base: "flex flex-col gap-1.5",
|
|
40
|
-
});
|
|
41
38
|
export const KanbanColumn = (props) => {
|
|
42
39
|
let ref;
|
|
43
40
|
const [dragging, setDragging] = createSignal("idle");
|
|
@@ -51,7 +48,7 @@ export const KanbanColumn = (props) => {
|
|
|
51
48
|
const stateKey = props.itemStateKey;
|
|
52
49
|
const posKey = props.itemPositionKey;
|
|
53
50
|
if (stateKey) {
|
|
54
|
-
items = items.filter((item) => item[stateKey] === props.col
|
|
51
|
+
items = items.filter((item) => item[stateKey] === props.col?.id);
|
|
55
52
|
}
|
|
56
53
|
if (!posKey)
|
|
57
54
|
return items;
|
|
@@ -60,7 +57,7 @@ export const KanbanColumn = (props) => {
|
|
|
60
57
|
const itemDragEnabled = () => !!props.itemPositionKey;
|
|
61
58
|
const [flashedCardId, setFlashedCardId] = createSignal(null);
|
|
62
59
|
createEffect(() => {
|
|
63
|
-
if (props.flashSignal?.() === props.col
|
|
60
|
+
if (props.flashSignal?.() === props.col?.id && ref) {
|
|
64
61
|
triggerFlash(ref);
|
|
65
62
|
}
|
|
66
63
|
});
|
|
@@ -77,7 +74,7 @@ export const KanbanColumn = (props) => {
|
|
|
77
74
|
return false;
|
|
78
75
|
}
|
|
79
76
|
// only allowing same collection for now to be dropped on me
|
|
80
|
-
return source.data.item.collectionId == props.col
|
|
77
|
+
return source.data.item.collectionId == props.col?.collectionId;
|
|
81
78
|
},
|
|
82
79
|
getData({ input }) {
|
|
83
80
|
return attachClosestEdge({
|
|
@@ -201,7 +198,7 @@ export const KanbanColumn = (props) => {
|
|
|
201
198
|
return {};
|
|
202
199
|
});
|
|
203
200
|
return (<div data-drop-edge={dragging() === "dragging-over" && !cardDraggedOver() ? (closestEdge() ?? undefined) : undefined} ref={ref} class={column({ class: props.class, folded: folded() })} style={{ opacity: dragging() == "dragging" ? 0.2 : 1, ...bgStyle() }}>
|
|
204
|
-
<
|
|
201
|
+
<Show when={props.col}>
|
|
205
202
|
<div class={columnHeader({ folded: folded() })}>
|
|
206
203
|
<div class="flex items-center gap-2">
|
|
207
204
|
<Button size="xs" variant="ghost" modifier="square" onClick={() => setFolded(!folded())}>
|
|
@@ -224,33 +221,31 @@ export const KanbanColumn = (props) => {
|
|
|
224
221
|
<span class="text-xs font-normal text-base-content/50 mt-2">{filteredItems().length}</span>
|
|
225
222
|
</div>)}
|
|
226
223
|
</div>
|
|
227
|
-
</
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
<Button appearance="neutral" size="sm" onClick={() => {
|
|
224
|
+
</Show>
|
|
225
|
+
{!folded() && (<>
|
|
226
|
+
{creatingItem() && props.col && (<div class="card bg-base-100 p-2 rounded-md space-y-1.5">
|
|
227
|
+
<p class="font-medium text-sm">New Item</p>
|
|
228
|
+
<Input value={newItemTitle()} onChange={setNewItemTitle} label="Title"/>
|
|
229
|
+
<div class="flex justify-end space-x-1.5">
|
|
230
|
+
<Button appearance="neutral" size="sm" onClick={() => {
|
|
235
231
|
setCreatingItem(false);
|
|
236
232
|
setNewItemTitle("");
|
|
237
233
|
}}>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
Cancel
|
|
235
|
+
</Button>
|
|
236
|
+
<Button appearance="success" size="sm" onClick={() => {
|
|
241
237
|
setCreatingItem(false);
|
|
242
238
|
props.onCreateItem?.(newItemTitle(), props.col.id);
|
|
243
239
|
setNewItemTitle("");
|
|
244
240
|
}}>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
</div>
|
|
241
|
+
Add
|
|
242
|
+
</Button>
|
|
243
|
+
</div>
|
|
244
|
+
</div>)}
|
|
245
|
+
<For each={filteredItems()}>
|
|
246
|
+
{(item) => (<KanbanCard item={item} dragEnabled={itemDragEnabled} onCardClick={() => props.onCardClick?.(item)} class={props.cardClass} renderItem={props.renderItem} flashSignal={() => flashedCardId()}/>)}
|
|
247
|
+
</For>
|
|
248
|
+
</>)}
|
|
254
249
|
</div>);
|
|
255
250
|
};
|
|
256
251
|
export default KanbanColumn;
|
|
@@ -3,7 +3,9 @@ export interface NavbarProps {
|
|
|
3
3
|
class?: string;
|
|
4
4
|
}
|
|
5
5
|
export interface NavbarComponents {
|
|
6
|
-
Brand: ParentComponent
|
|
6
|
+
Brand: ParentComponent<{
|
|
7
|
+
href?: string;
|
|
8
|
+
}>;
|
|
7
9
|
Profile: ParentComponent;
|
|
8
10
|
Menu: ParentComponent;
|
|
9
11
|
MenuItem: ParentComponent;
|
|
@@ -12,7 +14,9 @@ export interface NavbarComponents {
|
|
|
12
14
|
}>;
|
|
13
15
|
}
|
|
14
16
|
export declare const Navbar: ParentComponent<NavbarProps> & NavbarComponents;
|
|
15
|
-
export declare const NavbarBrand: ParentComponent
|
|
17
|
+
export declare const NavbarBrand: ParentComponent<{
|
|
18
|
+
href?: string;
|
|
19
|
+
}>;
|
|
16
20
|
export declare const NavbarProfile: ParentComponent;
|
|
17
21
|
export declare const NavbarMenu: ParentComponent;
|
|
18
22
|
export declare const NavbarSubmenu: ParentComponent<{
|
|
@@ -7,7 +7,9 @@ export const Navbar = (props) => {
|
|
|
7
7
|
return <nav class={navbar({ class: props.class })}>{props.children}</nav>;
|
|
8
8
|
};
|
|
9
9
|
export const NavbarBrand = (props) => {
|
|
10
|
-
return <
|
|
10
|
+
return (<a class="btn btn-lg btn-ghost" href={props.href}>
|
|
11
|
+
{props.children}
|
|
12
|
+
</a>);
|
|
11
13
|
};
|
|
12
14
|
export const NavbarProfile = (props) => {
|
|
13
15
|
return <div class="avatar">{props.children}</div>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JSXElement } from "solid-js";
|
|
2
|
+
export interface RelationPickerProps<T> {
|
|
3
|
+
value: T | T[] | null;
|
|
4
|
+
options: T[];
|
|
5
|
+
onChange: (val: T | T[] | null) => void;
|
|
6
|
+
labelKey: keyof T;
|
|
7
|
+
valueKey: keyof T;
|
|
8
|
+
disabledKey?: keyof T;
|
|
9
|
+
multi?: boolean;
|
|
10
|
+
label?: string;
|
|
11
|
+
variant?: "ghost";
|
|
12
|
+
appearance?: "neutral" | "primary" | "secondary" | "accent" | "info" | "success" | "warning" | "error";
|
|
13
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
listboxAction?: JSXElement;
|
|
18
|
+
onTextInputChange?: (text: string) => void;
|
|
19
|
+
defaultFilter?: (option: T[] | Exclude<NonNullable<T>, null>, filter: string) => boolean;
|
|
20
|
+
onLinkClick?: (value: T) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare const RelationPicker: <T>(props: RelationPickerProps<T>) => import("solid-js").JSX.Element;
|
|
23
|
+
export default RelationPicker;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Combobox } from "@kobalte/core/combobox";
|
|
2
|
+
import Check from "lucide-solid/icons/check";
|
|
3
|
+
import Link from "lucide-solid/icons/link";
|
|
4
|
+
import UpDown from "lucide-solid/icons/chevrons-up-down";
|
|
5
|
+
import { For, Show } from "solid-js";
|
|
6
|
+
import { tv } from "tailwind-variants";
|
|
7
|
+
import { Tag } from "./Tag";
|
|
8
|
+
import { Button } from "./Button";
|
|
9
|
+
import { iconSize } from "../constants";
|
|
10
|
+
const input = tv({
|
|
11
|
+
base: "join-item input outline-offset-0",
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
ghost: "input-ghost",
|
|
15
|
+
none: "",
|
|
16
|
+
},
|
|
17
|
+
appearance: {
|
|
18
|
+
neutral: "input-neutral",
|
|
19
|
+
primary: "input-primary",
|
|
20
|
+
secondary: "input-secondary",
|
|
21
|
+
accent: "input-accent",
|
|
22
|
+
info: "input-info",
|
|
23
|
+
success: "input-success",
|
|
24
|
+
warning: "input-warning",
|
|
25
|
+
error: "input-error",
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
xs: "input-xs",
|
|
29
|
+
sm: "input-sm",
|
|
30
|
+
md: "input-md",
|
|
31
|
+
lg: "input-lg",
|
|
32
|
+
xl: "input-xl",
|
|
33
|
+
},
|
|
34
|
+
tags: {
|
|
35
|
+
true: "h-full py-1.25",
|
|
36
|
+
false: "",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
size: "sm",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const menu = tv({
|
|
44
|
+
base: "menu w-full",
|
|
45
|
+
variants: {
|
|
46
|
+
size: {
|
|
47
|
+
xs: "menu-xs",
|
|
48
|
+
sm: "menu-sm",
|
|
49
|
+
md: "menu-base",
|
|
50
|
+
lg: "menu-lg",
|
|
51
|
+
xl: "menu-xl",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: {
|
|
55
|
+
size: "sm",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
export const RelationPicker = (props) => {
|
|
59
|
+
let inputRef;
|
|
60
|
+
const values = () => {
|
|
61
|
+
if (props.multi) {
|
|
62
|
+
return Array.isArray(props.value) ? props.value : props.value ? [props.value] : [];
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return props.value && !Array.isArray(props.value) ? props.value : null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const options = () => {
|
|
69
|
+
if (props.options.length === 0)
|
|
70
|
+
return [
|
|
71
|
+
{ [props.labelKey]: "No Records Found", [props.valueKey]: "__no_options__", disabled: true },
|
|
72
|
+
];
|
|
73
|
+
return props.options;
|
|
74
|
+
};
|
|
75
|
+
return (<div class="floating-label">
|
|
76
|
+
{props.label && <span>{props.label}</span>}
|
|
77
|
+
<Combobox disabled={props.disabled} multiple={props.multi} value={values()} onChange={props.onChange} options={options()}
|
|
78
|
+
//@ts-ignore, kobalte confusing, just ignore for now...
|
|
79
|
+
optionValue={props.valueKey}
|
|
80
|
+
//@ts-ignore
|
|
81
|
+
optionTextValue={props.labelKey}
|
|
82
|
+
//@ts-ignore
|
|
83
|
+
optionLabel={props.labelKey}
|
|
84
|
+
//@ts-ignore
|
|
85
|
+
optionDisabled={props.disabledKey} placeholder={props.placeholder} onMouseDown={(e) => {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
inputRef?.focus();
|
|
88
|
+
}} defaultFilter={props.defaultFilter} itemComponent={(itemProps) => (<Combobox.Item item={itemProps.item} class="outline-none focus:bg-base-300 rounded-sm">
|
|
89
|
+
<Combobox.ItemLabel class="flex flex-row justify-between items-center">
|
|
90
|
+
{itemProps.item.textValue}
|
|
91
|
+
<Combobox.ItemIndicator>
|
|
92
|
+
<Check size={16}/>
|
|
93
|
+
</Combobox.ItemIndicator>
|
|
94
|
+
</Combobox.ItemLabel>
|
|
95
|
+
</Combobox.Item>)}>
|
|
96
|
+
<Combobox.Control class="join w-full max-w-[20rem]">
|
|
97
|
+
{(state) => (<>
|
|
98
|
+
<div class={input({
|
|
99
|
+
variant: props.variant,
|
|
100
|
+
appearance: props.appearance,
|
|
101
|
+
size: props.size,
|
|
102
|
+
class: props.class,
|
|
103
|
+
tags: props.multi && Array.isArray(props.value) && props.value.length > 0,
|
|
104
|
+
})}>
|
|
105
|
+
<Show when={props.multi} fallback={<>
|
|
106
|
+
{!props.multi && values() && (<Button variant="ghost" appearance="primary" size="xs" modifier="square" onClick={() => props.onLinkClick?.(props.value)}>
|
|
107
|
+
<Link class="w-[1em] h-[1em]"/>
|
|
108
|
+
</Button>)}
|
|
109
|
+
<Combobox.Input onBlur={(e) => {
|
|
110
|
+
if (!props.value) {
|
|
111
|
+
e.currentTarget.value = "";
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
e.currentTarget.value = String(state.selectedOptions()[0][props.labelKey]);
|
|
115
|
+
}
|
|
116
|
+
}} ref={inputRef} onInput={(e) => props.onTextInputChange?.(e.currentTarget.value)}/>
|
|
117
|
+
</>}>
|
|
118
|
+
<div class="flex flex-wrap gap-1 w-full">
|
|
119
|
+
<For each={state.selectedOptions()}>
|
|
120
|
+
{(option) => (<span onPointerDown={(e) => e.stopPropagation()}>
|
|
121
|
+
<Tag appearance="neutral" variant="soft" title={String(option[props.labelKey])} onDelete={() => state.remove(option)}/>
|
|
122
|
+
</span>)}
|
|
123
|
+
</For>
|
|
124
|
+
<Combobox.Input class="w-[unset]" onBlur={(e) => (e.currentTarget.value = "")} ref={inputRef} onInput={(e) => props.onTextInputChange?.(e.currentTarget.value)}/>
|
|
125
|
+
</div>
|
|
126
|
+
</Show>
|
|
127
|
+
</div>
|
|
128
|
+
<Combobox.Trigger as={Button} size={props.size} modifier="square" class="join-item">
|
|
129
|
+
<Combobox.Icon>
|
|
130
|
+
<UpDown size={iconSize[props.size ?? "sm"]}/>
|
|
131
|
+
</Combobox.Icon>
|
|
132
|
+
</Combobox.Trigger>
|
|
133
|
+
</>)}
|
|
134
|
+
</Combobox.Control>
|
|
135
|
+
<Combobox.Portal>
|
|
136
|
+
<Combobox.Content class="rounded-box bg-base-100 shadow-md border border-base-200 z-20 max-h-100 overflow-auto">
|
|
137
|
+
<Combobox.Listbox class={menu({ size: props.size })}/>
|
|
138
|
+
{props.listboxAction}
|
|
139
|
+
</Combobox.Content>
|
|
140
|
+
</Combobox.Portal>
|
|
141
|
+
</Combobox>
|
|
142
|
+
</div>);
|
|
143
|
+
};
|
|
144
|
+
export default RelationPicker;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Select as KSelect } from "@kobalte/core/select";
|
|
2
2
|
import Check from "lucide-solid/icons/check";
|
|
3
|
-
import
|
|
3
|
+
import UpDown from "lucide-solid/icons/chevrons-up-down";
|
|
4
4
|
import { tv } from "tailwind-variants";
|
|
5
|
+
import { Button } from "../Button";
|
|
6
|
+
import { iconSize } from "../../constants";
|
|
5
7
|
const trigger = tv({
|
|
6
|
-
base: "input outline-offset-0 flex justify-between items-center",
|
|
8
|
+
base: "input outline-offset-0 flex justify-between items-center join-item",
|
|
7
9
|
variants: {
|
|
8
10
|
variant: {
|
|
9
11
|
ghost: "input-ghost",
|
|
@@ -59,21 +61,23 @@ export const Select = (props) => {
|
|
|
59
61
|
</KSelect.ItemLabel>
|
|
60
62
|
</KSelect.Item>);
|
|
61
63
|
}}>
|
|
62
|
-
<KSelect.Trigger class=
|
|
64
|
+
<KSelect.Trigger class="join w-full max-w-[20rem]">
|
|
65
|
+
<div class={trigger({
|
|
63
66
|
variant: props.variant,
|
|
64
67
|
appearance: props.appearance,
|
|
65
68
|
size: props.size,
|
|
66
69
|
class: props.class,
|
|
67
70
|
})}>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
<KSelect.Value>
|
|
72
|
+
{(state) => String(props.labelKey ? state.selectedOption()?.[props.labelKey] : "")}
|
|
73
|
+
</KSelect.Value>
|
|
74
|
+
</div>
|
|
75
|
+
<KSelect.Icon as={Button} class="join-item" modifier="square" size={props.size}>
|
|
76
|
+
<UpDown size={iconSize[props.size ?? "sm"]}/>
|
|
73
77
|
</KSelect.Icon>
|
|
74
78
|
</KSelect.Trigger>
|
|
75
79
|
<KSelect.Portal>
|
|
76
|
-
<KSelect.Content class="rounded-box bg-base-100 shadow-md border border-base-200 z-20">
|
|
80
|
+
<KSelect.Content class="rounded-box bg-base-100 shadow-md border border-base-200 z-20 max-h-100 overflow-auto">
|
|
77
81
|
<KSelect.Listbox class={menu({ size: props.size })}/>
|
|
78
82
|
</KSelect.Content>
|
|
79
83
|
</KSelect.Portal>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSXElement } from "solid-js";
|
|
2
2
|
import { ColumnDef, Row } from "@tanstack/solid-table";
|
|
3
3
|
interface TableItem {
|
|
4
4
|
id: string;
|
|
@@ -9,8 +9,8 @@ interface TableProps<T extends TableItem> {
|
|
|
9
9
|
data: T[];
|
|
10
10
|
createFunc?: () => Promise<void>;
|
|
11
11
|
headerActions?: JSXElement;
|
|
12
|
-
columns:
|
|
13
|
-
onRowClick
|
|
12
|
+
columns: ColumnDef<T>[];
|
|
13
|
+
onRowClick?: (item: T) => void;
|
|
14
14
|
loading?: boolean;
|
|
15
15
|
emptyState?: JSXElement;
|
|
16
16
|
loadingFallback?: JSXElement;
|
|
@@ -116,7 +116,7 @@ export const Table = (props) => {
|
|
|
116
116
|
get data() {
|
|
117
117
|
return props.data || [];
|
|
118
118
|
},
|
|
119
|
-
columns: props.columns
|
|
119
|
+
columns: props.columns,
|
|
120
120
|
getCoreRowModel: getCoreRowModel(),
|
|
121
121
|
});
|
|
122
122
|
const rowCount = createMemo(() => table.getRowModel().rows.length);
|
|
@@ -164,7 +164,7 @@ export const Table = (props) => {
|
|
|
164
164
|
<Loader class="w-9 h-9 animate-spin"/>
|
|
165
165
|
</div>)}>
|
|
166
166
|
<Show when={rowCount() > 0} fallback={props.emptyState || <div class="text-center py-4">No results found.</div>}>
|
|
167
|
-
<table class={tableClass({ class: props.class })}>
|
|
167
|
+
<table class={tableClass({ class: props.class, size: props.size })}>
|
|
168
168
|
<Show when={props.headers}>
|
|
169
169
|
<thead>
|
|
170
170
|
<For each={table.getHeaderGroups()}>
|
|
@@ -182,7 +182,7 @@ export const Table = (props) => {
|
|
|
182
182
|
|
|
183
183
|
<tbody>
|
|
184
184
|
<For each={rows()}>
|
|
185
|
-
{(row, ind) => (<TableRow row={row} ind={ind()} onRowClick={() => props.onRowClick(row.original)} dragEnabled={dragEnabled} flashSignal={() => flashedRowId()}/>)}
|
|
185
|
+
{(row, ind) => (<TableRow row={row} ind={ind()} onRowClick={() => props.onRowClick?.(row.original)} dragEnabled={dragEnabled} flashSignal={() => flashedRowId()}/>)}
|
|
186
186
|
</For>
|
|
187
187
|
</tbody>
|
|
188
188
|
</table>
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { Component, JSXElement } from "solid-js";
|
|
2
|
+
export interface ThemeOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: JSXElement;
|
|
5
|
+
}
|
|
6
|
+
interface ThemeSwitchProps {
|
|
7
|
+
triggerClass?: string;
|
|
8
|
+
options: ThemeOption[];
|
|
9
|
+
}
|
|
10
|
+
export declare const ThemeSwitch: Component<ThemeSwitchProps>;
|
|
2
11
|
export default ThemeSwitch;
|
|
@@ -1,72 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Sun from "lucide-solid/icons/sun";
|
|
3
|
-
import Moon from "lucide-solid/icons/moon";
|
|
1
|
+
import { For } from "solid-js";
|
|
4
2
|
import Monitor from "lucide-solid/icons/monitor";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
5
4
|
import { DropdownMenu } from "../DropdownMenu";
|
|
6
|
-
import {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (theme === "system") {
|
|
17
|
-
applied = getSystemTheme();
|
|
18
|
-
}
|
|
19
|
-
html.setAttribute("data-theme", applied);
|
|
20
|
-
}
|
|
21
|
-
const labelClass = "flex items-center gap-1";
|
|
22
|
-
const getThemeValue = (theme) => {
|
|
23
|
-
switch (theme) {
|
|
24
|
-
case "light":
|
|
25
|
-
return (<span class={labelClass}>
|
|
26
|
-
<Sun class="w-[1em] h-[1em]"/> Light
|
|
27
|
-
</span>);
|
|
28
|
-
case "dark":
|
|
29
|
-
return (<span class={labelClass}>
|
|
30
|
-
<Moon class="w-[1em] h-[1em]"/> Dark
|
|
31
|
-
</span>);
|
|
32
|
-
case "system":
|
|
33
|
-
return (<span class={labelClass}>
|
|
34
|
-
<Monitor class="w-[1em] h-[1em]"/> System
|
|
35
|
-
</span>);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
export function ThemeSwitch() {
|
|
39
|
-
const [theme, setTheme] = createSignal("system");
|
|
40
|
-
onMount(() => {
|
|
41
|
-
const saved = localStorage.getItem(THEME_KEY);
|
|
42
|
-
if (saved) {
|
|
43
|
-
setTheme(saved);
|
|
44
|
-
applyTheme(saved);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
applyTheme("system");
|
|
48
|
-
}
|
|
49
|
-
});
|
|
5
|
+
import { THEME_KEY } from "../../constants";
|
|
6
|
+
const trigger = tv({
|
|
7
|
+
base: "min-w-30",
|
|
8
|
+
});
|
|
9
|
+
const SystemOption = () => (<span class="flex items-center gap-1">
|
|
10
|
+
<Monitor class="w-[1em] h-[1em]"/> System
|
|
11
|
+
</span>);
|
|
12
|
+
export const ThemeSwitch = (props) => {
|
|
13
|
+
const options = () => props.options;
|
|
14
|
+
const theme = localStorage.getItem("theme");
|
|
50
15
|
const handleChange = (val) => {
|
|
51
|
-
|
|
16
|
+
if (val === "system") {
|
|
17
|
+
localStorage.removeItem(THEME_KEY);
|
|
18
|
+
document.documentElement.removeAttribute("data-theme");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
52
21
|
localStorage.setItem(THEME_KEY, val);
|
|
53
|
-
|
|
22
|
+
document.documentElement.setAttribute("data-theme", val);
|
|
23
|
+
};
|
|
24
|
+
const getCurrentLabel = () => {
|
|
25
|
+
if (theme === "system") {
|
|
26
|
+
return <SystemOption />;
|
|
27
|
+
}
|
|
28
|
+
const option = options().find((opt) => opt.value === theme);
|
|
29
|
+
return option?.label || theme;
|
|
54
30
|
};
|
|
55
31
|
return (<DropdownMenu>
|
|
56
|
-
<DropdownMenu.Trigger>
|
|
57
|
-
|
|
32
|
+
<DropdownMenu.Trigger class={trigger({ class: props.triggerClass })}>
|
|
33
|
+
{getCurrentLabel()}
|
|
58
34
|
</DropdownMenu.Trigger>
|
|
59
35
|
<DropdownMenu.Content>
|
|
60
|
-
<
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
</DropdownMenu.MenuItem>
|
|
36
|
+
<For each={options()}>
|
|
37
|
+
{(option) => (<DropdownMenu.MenuItem onSelect={() => handleChange(option.value)}>
|
|
38
|
+
{option.label}
|
|
39
|
+
</DropdownMenu.MenuItem>)}
|
|
40
|
+
</For>
|
|
66
41
|
<DropdownMenu.MenuItem onSelect={() => handleChange("system")}>
|
|
67
|
-
|
|
42
|
+
<SystemOption />
|
|
68
43
|
</DropdownMenu.MenuItem>
|
|
69
44
|
</DropdownMenu.Content>
|
|
70
45
|
</DropdownMenu>);
|
|
71
|
-
}
|
|
46
|
+
};
|
|
72
47
|
export default ThemeSwitch;
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./Form";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./Form";
|
|
File without changes
|
|
File without changes
|