@lobb-js/studio 0.10.0 → 0.11.1
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/dataTable/enumBadge.svelte +33 -0
- package/dist/components/dataTable/enumBadge.svelte.d.ts +8 -0
- package/dist/components/dataTable/fieldCell.svelte +3 -0
- package/dist/components/detailView/fieldInput.svelte +34 -0
- package/dist/components/drawer.svelte +17 -14
- package/package.json +4 -1
- package/src/lib/components/dataTable/enumBadge.svelte +33 -0
- package/src/lib/components/dataTable/fieldCell.svelte +3 -0
- package/src/lib/components/detailView/fieldInput.svelte +34 -0
- package/src/lib/components/drawer.svelte +17 -14
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { EnumOption } from "@lobb-js/core";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: any;
|
|
6
|
+
enum: EnumOption[] | string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { value, enum: enumOptions }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const levelClasses: Record<string, string> = {
|
|
12
|
+
success: "bg-green-100 text-green-700 border-green-200",
|
|
13
|
+
warning: "bg-yellow-100 text-yellow-700 border-yellow-300",
|
|
14
|
+
danger: "bg-red-100 text-red-700 border-red-200",
|
|
15
|
+
info: "bg-blue-100 text-blue-700 border-blue-200",
|
|
16
|
+
neutral: "bg-gray-100 text-gray-600 border-gray-200",
|
|
17
|
+
muted: "bg-gray-50 text-gray-400 border-gray-100",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const normalizedOptions = $derived(
|
|
21
|
+
(enumOptions as Array<string | EnumOption>).map((e) =>
|
|
22
|
+
typeof e === "string" ? { value: e, level: "neutral" as const } : e,
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const enumOption = $derived(normalizedOptions.find((e) => e.value === value));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
{#if enumOption}
|
|
30
|
+
<span class="px-2 py-0.5 rounded-full text-xs font-medium border {levelClasses[enumOption.level]}">
|
|
31
|
+
{value}
|
|
32
|
+
</span>
|
|
33
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EnumOption } from "@lobb-js/core";
|
|
2
|
+
interface Props {
|
|
3
|
+
value: any;
|
|
4
|
+
enum: EnumOption[] | string[];
|
|
5
|
+
}
|
|
6
|
+
declare const EnumBadge: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type EnumBadge = ReturnType<typeof EnumBadge>;
|
|
8
|
+
export default EnumBadge;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ExternalLink } from "lucide-svelte";
|
|
4
4
|
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
5
5
|
import { getField } from "./utils";
|
|
6
|
+
import EnumBadge from "./enumBadge.svelte";
|
|
6
7
|
import _ from "lodash";
|
|
7
8
|
import { getStudioContext } from "../../context";
|
|
8
9
|
|
|
@@ -66,6 +67,8 @@
|
|
|
66
67
|
{:else}
|
|
67
68
|
<div class="text-muted-foreground">PARENT ID</div>
|
|
68
69
|
{/if}
|
|
70
|
+
{:else if field?.enum}
|
|
71
|
+
<EnumBadge {value} enum={field.enum} />
|
|
69
72
|
{:else if field.type === "datetime"}
|
|
70
73
|
{@const date = new Date(value).toLocaleDateString()}
|
|
71
74
|
{@const time = new Date(value).toLocaleTimeString()}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import FieldCustomInput from "./fieldCustomInput.svelte";
|
|
8
8
|
import Input from "../ui/input/input.svelte";
|
|
9
9
|
import * as Select from "../../components/ui/select/index";
|
|
10
|
+
import EnumBadge from "../dataTable/enumBadge.svelte";
|
|
11
|
+
import type { EnumOption } from "@lobb-js/core";
|
|
10
12
|
import Textarea from "../ui/textarea/textarea.svelte";
|
|
11
13
|
import ForeingKeyInput from "../foreingKeyInput.svelte";
|
|
12
14
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
@@ -91,6 +93,38 @@
|
|
|
91
93
|
{destructive}
|
|
92
94
|
/>
|
|
93
95
|
</ExtensionsComponents>
|
|
96
|
+
{:else if field.type === "string" && field.enum}
|
|
97
|
+
{@const rawEnum = field.enum as (string | EnumOption)[]}
|
|
98
|
+
{@const enumOptions = rawEnum.map((e): EnumOption => typeof e === "string" ? { value: e, level: "neutral" } : e)}
|
|
99
|
+
<Select.Root
|
|
100
|
+
type="single"
|
|
101
|
+
onValueChange={(newValue) => {
|
|
102
|
+
value = newValue;
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<Select.Trigger
|
|
106
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
107
|
+
class="
|
|
108
|
+
h-9 w-full bg-muted/30 pr-8
|
|
109
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
110
|
+
"
|
|
111
|
+
>
|
|
112
|
+
{#if value}
|
|
113
|
+
<EnumBadge {value} enum={enumOptions} />
|
|
114
|
+
{:else}
|
|
115
|
+
NULL
|
|
116
|
+
{/if}
|
|
117
|
+
</Select.Trigger>
|
|
118
|
+
<Select.Content>
|
|
119
|
+
<Select.Group>
|
|
120
|
+
{#each enumOptions as option}
|
|
121
|
+
<Select.Item value={option.value} label={option.value}>
|
|
122
|
+
<EnumBadge value={option.value} enum={enumOptions} />
|
|
123
|
+
</Select.Item>
|
|
124
|
+
{/each}
|
|
125
|
+
</Select.Group>
|
|
126
|
+
</Select.Content>
|
|
127
|
+
</Select.Root>
|
|
94
128
|
{:else if field.type === "string"}
|
|
95
129
|
<!-- if the string has a validator of type enum -->
|
|
96
130
|
{#if field.validators && field.validators.enum}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { calculateDrawerWidth } from "../utils";
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
4
|
import { fade, fly } from "svelte/transition";
|
|
5
|
+
import Portal from "svelte-portal";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
children?: Snippet<[]>;
|
|
@@ -11,18 +12,20 @@
|
|
|
11
12
|
let { onHide, children }: Props = $props();
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<Portal target="body">
|
|
16
|
+
<button
|
|
17
|
+
transition:fade={{ duration: 250 }}
|
|
18
|
+
onclick={() => onHide?.()}
|
|
19
|
+
class="backgroundDrawerButton fixed left-0 top-0 z-40 h-screen w-screen bg-background opacity-50 cursor-default"
|
|
20
|
+
aria-label="background used to hide the background"
|
|
21
|
+
></button>
|
|
20
22
|
|
|
21
|
-
<!-- the drawer -->
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
>
|
|
27
|
-
|
|
28
|
-
</div>
|
|
23
|
+
<!-- the drawer -->
|
|
24
|
+
<div
|
|
25
|
+
transition:fly={{ x: "100%", duration: 250 }}
|
|
26
|
+
class="fixed right-0 top-0 z-40 flex h-full w-full flex-col border-l bg-background"
|
|
27
|
+
style="max-width: {calculateDrawerWidth()}px;"
|
|
28
|
+
>
|
|
29
|
+
{@render children?.()}
|
|
30
|
+
</div>
|
|
31
|
+
</Portal>
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/studio",
|
|
3
|
-
"
|
|
3
|
+
"license": "UNLICENSED",
|
|
4
|
+
"version": "0.11.1",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"publishConfig": {
|
|
6
7
|
"access": "public"
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"postpublish": "./scripts/postpublish.sh"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
45
|
+
"@lobb-js/core": "^0.18.0",
|
|
44
46
|
"@chromatic-com/storybook": "^4.1.2",
|
|
45
47
|
"@storybook/addon-a11y": "^10.0.1",
|
|
46
48
|
"@storybook/addon-docs": "^10.0.1",
|
|
@@ -106,6 +108,7 @@
|
|
|
106
108
|
"mode-watcher": "^0.5.1",
|
|
107
109
|
"mustache": "^4.2.0",
|
|
108
110
|
"qs": "^6.14.1",
|
|
111
|
+
"svelte-portal": "^2.2.1",
|
|
109
112
|
"svelte-sonner": "^0.3.28",
|
|
110
113
|
"tailwind-merge": "^3.4.0",
|
|
111
114
|
"tailwind-variants": "^3.2.2"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { EnumOption } from "@lobb-js/core";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: any;
|
|
6
|
+
enum: EnumOption[] | string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { value, enum: enumOptions }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const levelClasses: Record<string, string> = {
|
|
12
|
+
success: "bg-green-100 text-green-700 border-green-200",
|
|
13
|
+
warning: "bg-yellow-100 text-yellow-700 border-yellow-300",
|
|
14
|
+
danger: "bg-red-100 text-red-700 border-red-200",
|
|
15
|
+
info: "bg-blue-100 text-blue-700 border-blue-200",
|
|
16
|
+
neutral: "bg-gray-100 text-gray-600 border-gray-200",
|
|
17
|
+
muted: "bg-gray-50 text-gray-400 border-gray-100",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const normalizedOptions = $derived(
|
|
21
|
+
(enumOptions as Array<string | EnumOption>).map((e) =>
|
|
22
|
+
typeof e === "string" ? { value: e, level: "neutral" as const } : e,
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const enumOption = $derived(normalizedOptions.find((e) => e.value === value));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
{#if enumOption}
|
|
30
|
+
<span class="px-2 py-0.5 rounded-full text-xs font-medium border {levelClasses[enumOption.level]}">
|
|
31
|
+
{value}
|
|
32
|
+
</span>
|
|
33
|
+
{/if}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ExternalLink } from "lucide-svelte";
|
|
4
4
|
import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
|
|
5
5
|
import { getField } from "./utils";
|
|
6
|
+
import EnumBadge from "./enumBadge.svelte";
|
|
6
7
|
import _ from "lodash";
|
|
7
8
|
import { getStudioContext } from "../../context";
|
|
8
9
|
|
|
@@ -66,6 +67,8 @@
|
|
|
66
67
|
{:else}
|
|
67
68
|
<div class="text-muted-foreground">PARENT ID</div>
|
|
68
69
|
{/if}
|
|
70
|
+
{:else if field?.enum}
|
|
71
|
+
<EnumBadge {value} enum={field.enum} />
|
|
69
72
|
{:else if field.type === "datetime"}
|
|
70
73
|
{@const date = new Date(value).toLocaleDateString()}
|
|
71
74
|
{@const time = new Date(value).toLocaleTimeString()}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import FieldCustomInput from "./fieldCustomInput.svelte";
|
|
8
8
|
import Input from "../ui/input/input.svelte";
|
|
9
9
|
import * as Select from "../../components/ui/select/index";
|
|
10
|
+
import EnumBadge from "../dataTable/enumBadge.svelte";
|
|
11
|
+
import type { EnumOption } from "@lobb-js/core";
|
|
10
12
|
import Textarea from "../ui/textarea/textarea.svelte";
|
|
11
13
|
import ForeingKeyInput from "../foreingKeyInput.svelte";
|
|
12
14
|
import ExtensionsComponents from "../extensionsComponents.svelte";
|
|
@@ -91,6 +93,38 @@
|
|
|
91
93
|
{destructive}
|
|
92
94
|
/>
|
|
93
95
|
</ExtensionsComponents>
|
|
96
|
+
{:else if field.type === "string" && field.enum}
|
|
97
|
+
{@const rawEnum = field.enum as (string | EnumOption)[]}
|
|
98
|
+
{@const enumOptions = rawEnum.map((e): EnumOption => typeof e === "string" ? { value: e, level: "neutral" } : e)}
|
|
99
|
+
<Select.Root
|
|
100
|
+
type="single"
|
|
101
|
+
onValueChange={(newValue) => {
|
|
102
|
+
value = newValue;
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<Select.Trigger
|
|
106
|
+
placeholder={ui?.placeholder ? ui.placeholder : "NULL"}
|
|
107
|
+
class="
|
|
108
|
+
h-9 w-full bg-muted/30 pr-8
|
|
109
|
+
{destructive ? 'border-destructive bg-destructive/10' : ''}
|
|
110
|
+
"
|
|
111
|
+
>
|
|
112
|
+
{#if value}
|
|
113
|
+
<EnumBadge {value} enum={enumOptions} />
|
|
114
|
+
{:else}
|
|
115
|
+
NULL
|
|
116
|
+
{/if}
|
|
117
|
+
</Select.Trigger>
|
|
118
|
+
<Select.Content>
|
|
119
|
+
<Select.Group>
|
|
120
|
+
{#each enumOptions as option}
|
|
121
|
+
<Select.Item value={option.value} label={option.value}>
|
|
122
|
+
<EnumBadge value={option.value} enum={enumOptions} />
|
|
123
|
+
</Select.Item>
|
|
124
|
+
{/each}
|
|
125
|
+
</Select.Group>
|
|
126
|
+
</Select.Content>
|
|
127
|
+
</Select.Root>
|
|
94
128
|
{:else if field.type === "string"}
|
|
95
129
|
<!-- if the string has a validator of type enum -->
|
|
96
130
|
{#if field.validators && field.validators.enum}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { calculateDrawerWidth } from "../utils";
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
4
|
import { fade, fly } from "svelte/transition";
|
|
5
|
+
import Portal from "svelte-portal";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
children?: Snippet<[]>;
|
|
@@ -11,18 +12,20 @@
|
|
|
11
12
|
let { onHide, children }: Props = $props();
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<Portal target="body">
|
|
16
|
+
<button
|
|
17
|
+
transition:fade={{ duration: 250 }}
|
|
18
|
+
onclick={() => onHide?.()}
|
|
19
|
+
class="backgroundDrawerButton fixed left-0 top-0 z-40 h-screen w-screen bg-background opacity-50 cursor-default"
|
|
20
|
+
aria-label="background used to hide the background"
|
|
21
|
+
></button>
|
|
20
22
|
|
|
21
|
-
<!-- the drawer -->
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
>
|
|
27
|
-
|
|
28
|
-
</div>
|
|
23
|
+
<!-- the drawer -->
|
|
24
|
+
<div
|
|
25
|
+
transition:fly={{ x: "100%", duration: 250 }}
|
|
26
|
+
class="fixed right-0 top-0 z-40 flex h-full w-full flex-col border-l bg-background"
|
|
27
|
+
style="max-width: {calculateDrawerWidth()}px;"
|
|
28
|
+
>
|
|
29
|
+
{@render children?.()}
|
|
30
|
+
</div>
|
|
31
|
+
</Portal>
|