@solidpb/ui-kit 0.2.0 → 0.3.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.
@@ -1,7 +1,6 @@
1
1
  import { ParentComponent } from "solid-js";
2
2
  interface Props {
3
3
  class?: string;
4
- noPadding?: boolean;
5
4
  }
6
5
  export declare const Container: ParentComponent<Props>;
7
6
  export default Container;
@@ -1,15 +1,9 @@
1
1
  import { tv } from "tailwind-variants";
2
2
  const container = tv({
3
- base: "flex-1 bg-base-100 dark:bg-base-900 min-h-[calc(100vh-4rem)]",
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({ noPadding: props.noPadding, class: props.class });
6
+ const classes = container({ class: props.class });
13
7
  return <div class={classes}>{props.children}</div>;
14
8
  };
15
9
  export default Container;
@@ -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;
@@ -14,6 +14,7 @@ export interface FormProps<T> {
14
14
  onSave?: (values: Partial<T>) => Promise<void>;
15
15
  onCancel?: () => void;
16
16
  children: JSXElement;
17
+ class?: string;
17
18
  }
18
19
  type BaseFieldProps<T> = {
19
20
  field: keyof T;
@@ -11,6 +11,10 @@ import { Slider } from "../Slider";
11
11
  import { Image } from "../Image";
12
12
  import { Button } from "../Button";
13
13
  import { FileInput } from "../FileInput";
14
+ import { tv } from "tailwind-variants";
15
+ const formClass = tv({
16
+ base: "space-y-4 space-x-4",
17
+ });
14
18
  export function createForm() {
15
19
  const Form = (props) => {
16
20
  const [values, setValues] = createStore({ ...props.data });
@@ -26,7 +30,7 @@ export function createForm() {
26
30
  props.onSave?.(values);
27
31
  };
28
32
  return (<InternalFormContext.Provider value={contextValue}>
29
- <form onSubmit={handleSubmit} class="space-y-4 space-x-4">
33
+ <form onSubmit={handleSubmit} class={formClass({ class: props.class })}>
30
34
  {props.title && <h2 class="text-lg font-semibold">{props.title}</h2>}
31
35
 
32
36
  {props.children}
@@ -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 shadow object-cover",
7
+ base: "rounded-box shadow 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-box shadow 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
- <img {...imgProps} src={preview() || props.src} alt={props.alt} class={image({ size: props.size, class: props.class })}/>
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-box">
56
74
  <Button size="sm" modifier="square" variant="ghost" onClick={handleEditClick}>
57
75
  <Pencil class="w-4 h-4"/>
58
76
  </Button>
@@ -1,6 +1,6 @@
1
1
  import { JSXElement } from "solid-js";
2
2
  export interface KanbanProps<T extends KanbanItem, K extends KanbanState> {
3
- columns: K[];
3
+ columns?: K[];
4
4
  items: T[];
5
5
  renderItem?: (item: T) => JSXElement;
6
6
  onCardClick?: (item: T) => void;
@@ -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.toSorted((a, b) => (Number(a[posKey]) ?? 0) - (Number(b[posKey]) ?? 0));
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
- <For each={sortedColumns()}>
62
- {(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}/>)}
63
- </For>
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;
@@ -1,6 +1,6 @@
1
1
  import { Accessor, JSXElement } from "solid-js";
2
2
  export interface KanbanColumnProps<T extends KanbanItem, K extends KanbanState> {
3
- col: K;
3
+ col?: K;
4
4
  items: T[];
5
5
  dragEnabled: Accessor<boolean>;
6
6
  class?: string;
@@ -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 flex-shrink-0 bg-base-300 p-1.5 rounded-md transition-[width] text-nowrap",
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.id);
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.id && ref) {
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.collectionId;
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
- <div>
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
- </div>
228
- <div class={columnContent()}>
229
- {!folded() && (<>
230
- {creatingItem() && (<div class="card bg-base-100 p-2 rounded-md space-y-1.5">
231
- <p class="font-medium text-sm">New Item</p>
232
- <Input value={newItemTitle()} onChange={setNewItemTitle} label="Title"/>
233
- <div class="flex justify-end space-x-1.5">
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
- Cancel
239
- </Button>
240
- <Button appearance="success" size="sm" onClick={() => {
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
- Add
246
- </Button>
247
- </div>
248
- </div>)}
249
- <For each={filteredItems()}>
250
- {(item) => (<KanbanCard item={item} dragEnabled={itemDragEnabled} onCardClick={() => props.onCardClick?.(item)} class={props.cardClass} renderItem={props.renderItem} flashSignal={() => flashedCardId()}/>)}
251
- </For>
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 <div class="btn btn-lg btn-ghost">{props.children}</div>;
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>;
@@ -1,4 +1,4 @@
1
- import { Accessor, JSXElement } from "solid-js";
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: Accessor<ColumnDef<T>[]>;
13
- onRowClick: (item: T) => void;
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);
@@ -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
- export declare function ThemeSwitch(): import("solid-js").JSX.Element;
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,49 @@
1
- import { createSignal, onMount } from "solid-js";
2
- import Sun from "lucide-solid/icons/sun";
3
- import Moon from "lucide-solid/icons/moon";
1
+ import { createSignal, 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 { Button } from "../Button";
7
- const THEME_KEY = "theme";
8
- function getSystemTheme() {
9
- if (window.matchMedia("(prefers-color-scheme: dark)").matches)
10
- return "dark";
11
- return "light";
12
- }
13
- function applyTheme(theme) {
14
- const html = document.documentElement;
15
- let applied = theme;
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() {
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;
39
14
  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
- });
50
15
  const handleChange = (val) => {
51
16
  setTheme(val);
17
+ if (val === "system") {
18
+ localStorage.removeItem(THEME_KEY);
19
+ document.documentElement.removeAttribute("data-theme");
20
+ return;
21
+ }
52
22
  localStorage.setItem(THEME_KEY, val);
53
- applyTheme(val);
23
+ document.documentElement.setAttribute("data-theme", val);
24
+ };
25
+ const getCurrentLabel = () => {
26
+ const current = theme();
27
+ if (current === "system") {
28
+ return <SystemOption />;
29
+ }
30
+ const option = options().find((opt) => opt.value === current);
31
+ return option?.label || current;
54
32
  };
55
33
  return (<DropdownMenu>
56
- <DropdownMenu.Trigger>
57
- <Button>{getThemeValue(theme())}</Button>
34
+ <DropdownMenu.Trigger class={trigger({ class: props.triggerClass })}>
35
+ {getCurrentLabel()}
58
36
  </DropdownMenu.Trigger>
59
37
  <DropdownMenu.Content>
60
- <DropdownMenu.MenuItem onSelect={() => handleChange("light")}>
61
- {getThemeValue("light")}
62
- </DropdownMenu.MenuItem>
63
- <DropdownMenu.MenuItem onSelect={() => handleChange("dark")}>
64
- {getThemeValue("dark")}
65
- </DropdownMenu.MenuItem>
38
+ <For each={options()}>
39
+ {(option) => (<DropdownMenu.MenuItem onSelect={() => handleChange(option.value)}>
40
+ {option.label}
41
+ </DropdownMenu.MenuItem>)}
42
+ </For>
66
43
  <DropdownMenu.MenuItem onSelect={() => handleChange("system")}>
67
- {getThemeValue("system")}
44
+ <SystemOption />
68
45
  </DropdownMenu.MenuItem>
69
46
  </DropdownMenu.Content>
70
47
  </DropdownMenu>);
71
- }
48
+ };
72
49
  export default ThemeSwitch;
@@ -5,3 +5,4 @@ export declare const iconSize: {
5
5
  lg: number;
6
6
  xl: number;
7
7
  };
8
+ export declare const THEME_KEY = "theme";
package/dist/constants.js CHANGED
@@ -5,3 +5,4 @@ export const iconSize = {
5
5
  lg: 20,
6
6
  xl: 22,
7
7
  };
8
+ export const THEME_KEY = "theme";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidpb/ui-kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",