Skip to content

Commit 958ade3

Browse files
authored
[builder] Small UX tweaks (#320)
* [builder] Add icon selection to the editor page * [builder] Make the tabs list sticky
1 parent f8c8bea commit 958ade3

File tree

7 files changed

+126
-100
lines changed

7 files changed

+126
-100
lines changed

.changeset/cozy-insects-wear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"builder": patch
3+
---
4+
5+
Add icon selection to the editor page

.changeset/dark-eagles-pick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"builder": patch
3+
---
4+
5+
Make the tabs list sticky

apps/builder/src/builder/editor/controls.svelte

Lines changed: 81 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,39 @@
22
import type { Component } from 'svelte';
33
import type { SVGAttributes } from 'svelte/elements';
44
5-
import MdiSegment from '~icons/mdi/segment';
6-
import MdiGridLarge from '~icons/mdi/grid-large';
7-
import MdiFormatListBulleted from '~icons/mdi/format-list-bulleted';
8-
import MdiRadioboxBlank from '~icons/mdi/radiobox-blank';
9-
import MdiCheckboxBlankOutline from '~icons/mdi/checkbox-blank-outline';
10-
import MdiCursorText from '~icons/mdi/cursor-text';
11-
import MdiHashtag from '~icons/mdi/hashtag';
12-
import MdiToggleSwitchOffOutline from '~icons/mdi/toggle-switch-off-outline';
13-
import MdiAttachFile from '~icons/mdi/attach-file';
14-
import MdiTag from '~icons/mdi/tag';
5+
import MdiSegment from '~icons/mdi/segment';
6+
import MdiGridLarge from '~icons/mdi/grid-large';
7+
import MdiFormatListBulleted from '~icons/mdi/format-list-bulleted';
8+
import MdiRadioboxBlank from '~icons/mdi/radiobox-blank';
9+
import MdiCheckboxBlankOutline from '~icons/mdi/checkbox-blank-outline';
10+
import MdiCursorText from '~icons/mdi/cursor-text';
11+
import MdiHashtag from '~icons/mdi/hashtag';
12+
import MdiToggleSwitchOffOutline from '~icons/mdi/toggle-switch-off-outline';
13+
import MdiAttachFile from '~icons/mdi/attach-file';
14+
import MdiTag from '~icons/mdi/tag';
1515
16-
import MdiSetAnd from '~icons/mdi/set-and';
17-
import MdiSetOr from '~icons/mdi/set-or';
18-
import MdiSetXor from '~icons/mdi/set-xor';
19-
import MdiSetNot from '~icons/mdi/set-not';
20-
import MdiEqual from '~icons/mdi/equal';
21-
import MdiFormatListChecks from '~icons/mdi/format-list-checks';
22-
import MdiRegex from '~icons/mdi/regex';
23-
import MdiArrowExpandHorizontal from '~icons/mdi/arrow-expand-horizontal';
24-
import MdiArrowCollapseHorizontal from '~icons/mdi/arrow-collapse-horizontal';
25-
import MdiLessThan from '~icons/mdi/less-than';
26-
import MdiLessThanOrEqual from '~icons/mdi/less-than-or-equal';
27-
import MdiGreaterThan from '~icons/mdi/greater-than';
28-
import MdiGreaterThanOrEqual from '~icons/mdi/greater-than-or-equal';
29-
import MdiMultiplication from '~icons/mdi/multiplication';
30-
import MdiContain from '~icons/mdi/contain';
31-
import MdiMinusBox from '~icons/mdi/minus-box';
32-
import MdiPlusBox from '~icons/mdi/plus-box';
33-
import MdiVectorArrangeAbove from '~icons/mdi/vector-arrange-above';
34-
import MdiPageNextOutline from '~icons/mdi/page-next-outline';
35-
import MdiFileTree from '~icons/mdi/file-tree';
36-
import MdiArrowLeftRight from '~icons/mdi/arrow-left-right';
37-
import MdiFormatText from '~icons/mdi/format-text';
16+
import MdiSetAnd from '~icons/mdi/set-and';
17+
import MdiSetOr from '~icons/mdi/set-or';
18+
import MdiSetXor from '~icons/mdi/set-xor';
19+
import MdiSetNot from '~icons/mdi/set-not';
20+
import MdiEqual from '~icons/mdi/equal';
21+
import MdiFormatListChecks from '~icons/mdi/format-list-checks';
22+
import MdiRegex from '~icons/mdi/regex';
23+
import MdiArrowExpandHorizontal from '~icons/mdi/arrow-expand-horizontal';
24+
import MdiArrowCollapseHorizontal from '~icons/mdi/arrow-collapse-horizontal';
25+
import MdiLessThan from '~icons/mdi/less-than';
26+
import MdiLessThanOrEqual from '~icons/mdi/less-than-or-equal';
27+
import MdiGreaterThan from '~icons/mdi/greater-than';
28+
import MdiGreaterThanOrEqual from '~icons/mdi/greater-than-or-equal';
29+
import MdiMultiplication from '~icons/mdi/multiplication';
30+
import MdiContain from '~icons/mdi/contain';
31+
import MdiMinusBox from '~icons/mdi/minus-box';
32+
import MdiPlusBox from '~icons/mdi/plus-box';
33+
import MdiVectorArrangeAbove from '~icons/mdi/vector-arrange-above';
34+
import MdiPageNextOutline from '~icons/mdi/page-next-outline';
35+
import MdiFileTree from '~icons/mdi/file-tree';
36+
import MdiArrowLeftRight from '~icons/mdi/arrow-left-right';
37+
import MdiFormatText from '~icons/mdi/format-text';
3838
3939
import {
4040
createNode,
@@ -52,52 +52,52 @@
5252
import NodeFactory from '../node-factory.svelte';
5353
5454
const NODE_ICONS: Record<NodeType, Component<SVGAttributes<SVGSVGElement>> | null> = {
55-
[NodeType.Object]: MdiSegment,
56-
[NodeType.ObjectProperty]: null,
57-
[NodeType.ObjectPropertyDependency]: null,
58-
[NodeType.Predicate]: null,
59-
[NodeType.Operator]: null,
60-
[NodeType.Array]: MdiFormatListBulleted,
61-
[NodeType.Grid]: MdiGridLarge,
62-
[NodeType.Enum]: MdiRadioboxBlank,
63-
[NodeType.MultiEnum]: MdiCheckboxBlankOutline,
64-
[NodeType.EnumItem]: null,
65-
[NodeType.String]: MdiCursorText,
66-
[NodeType.Number]: MdiHashtag,
67-
[NodeType.Boolean]: MdiToggleSwitchOffOutline,
68-
[NodeType.File]: MdiAttachFile,
69-
[NodeType.Tags]: MdiTag,
70-
[NodeType.Range]: MdiArrowLeftRight,
71-
};
55+
[NodeType.Object]: MdiSegment,
56+
[NodeType.ObjectProperty]: null,
57+
[NodeType.ObjectPropertyDependency]: null,
58+
[NodeType.Predicate]: null,
59+
[NodeType.Operator]: null,
60+
[NodeType.Array]: MdiFormatListBulleted,
61+
[NodeType.Grid]: MdiGridLarge,
62+
[NodeType.Enum]: MdiRadioboxBlank,
63+
[NodeType.MultiEnum]: MdiCheckboxBlankOutline,
64+
[NodeType.EnumItem]: null,
65+
[NodeType.String]: MdiCursorText,
66+
[NodeType.Number]: MdiHashtag,
67+
[NodeType.Boolean]: MdiToggleSwitchOffOutline,
68+
[NodeType.File]: MdiAttachFile,
69+
[NodeType.Tags]: MdiTag,
70+
[NodeType.Range]: MdiArrowLeftRight
71+
};
7272
73-
const OPERATOR_ICONS: Record<OperatorType, Component<SVGAttributes<SVGSVGElement>>> = {
74-
[OperatorType.And]: MdiSetAnd,
75-
[OperatorType.Or]: MdiSetOr,
76-
[OperatorType.Xor]: MdiSetXor,
77-
[OperatorType.Not]: MdiSetNot,
78-
// Shared
79-
[OperatorType.Eq]: MdiEqual,
80-
[OperatorType.In]: MdiFormatListChecks,
81-
// String
82-
[OperatorType.Format]: MdiFormatText,
83-
[OperatorType.Pattern]: MdiRegex,
84-
[OperatorType.MinLength]: MdiArrowCollapseHorizontal,
85-
[OperatorType.MaxLength]: MdiArrowExpandHorizontal,
86-
// Number
87-
[OperatorType.Less]: MdiLessThan,
88-
[OperatorType.LessOrEq]: MdiLessThanOrEqual,
89-
[OperatorType.Greater]: MdiGreaterThan,
90-
[OperatorType.GreaterOrEq]: MdiGreaterThanOrEqual,
91-
[OperatorType.MultipleOf]: MdiMultiplication,
92-
// Array
93-
[OperatorType.Contains]: MdiContain,
94-
[OperatorType.MinItems]: MdiMinusBox,
95-
[OperatorType.MaxItems]: MdiPlusBox,
96-
[OperatorType.UniqueItems]: MdiVectorArrangeAbove,
97-
// Object
98-
[OperatorType.HasProperty]: MdiPageNextOutline,
99-
[OperatorType.Property]: MdiFileTree,
100-
}
73+
const OPERATOR_ICONS: Record<OperatorType, Component<SVGAttributes<SVGSVGElement>>> = {
74+
[OperatorType.And]: MdiSetAnd,
75+
[OperatorType.Or]: MdiSetOr,
76+
[OperatorType.Xor]: MdiSetXor,
77+
[OperatorType.Not]: MdiSetNot,
78+
// Shared
79+
[OperatorType.Eq]: MdiEqual,
80+
[OperatorType.In]: MdiFormatListChecks,
81+
// String
82+
[OperatorType.Format]: MdiFormatText,
83+
[OperatorType.Pattern]: MdiRegex,
84+
[OperatorType.MinLength]: MdiArrowCollapseHorizontal,
85+
[OperatorType.MaxLength]: MdiArrowExpandHorizontal,
86+
// Number
87+
[OperatorType.Less]: MdiLessThan,
88+
[OperatorType.LessOrEq]: MdiLessThanOrEqual,
89+
[OperatorType.Greater]: MdiGreaterThan,
90+
[OperatorType.GreaterOrEq]: MdiGreaterThanOrEqual,
91+
[OperatorType.MultipleOf]: MdiMultiplication,
92+
// Array
93+
[OperatorType.Contains]: MdiContain,
94+
[OperatorType.MinItems]: MdiMinusBox,
95+
[OperatorType.MaxItems]: MdiPlusBox,
96+
[OperatorType.UniqueItems]: MdiVectorArrangeAbove,
97+
// Object
98+
[OperatorType.HasProperty]: MdiPageNextOutline,
99+
[OperatorType.Property]: MdiFileTree
100+
};
101101
102102
const ctx = getBuilderContext();
103103
const entries: {
@@ -132,8 +132,11 @@
132132
<NodeFactory createNode={factory} {title}>
133133
{#snippet icon()}
134134
{@const Icon = operatorType ? OPERATOR_ICONS[operatorType] : NODE_ICONS[nodeType]}
135-
<Icon />
135+
<Icon />
136136
{/snippet}
137137
</NodeFactory>
138138
{/each}
139+
<p class="pt-2 text-sm text-muted-foreground">
140+
The set of available fields and their corresponding widgets depends on the selected theme.
141+
</p>
139142
</div>

apps/builder/src/builder/editor/settings.svelte

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
import { CopyButton } from '$lib/components/copy-button/index.js';
88
import { Label } from '$lib/components/ui/label/index.js';
99
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
10-
import { THEME_TITLES, THEMES } from '$lib/sjsf/theme.js';
1110
import Select from '$lib/components/select.svelte';
11+
import { THEME_TITLES, THEMES } from '$lib/sjsf/theme.js';
12+
import { ICONS, ICONS_TITLES } from '$lib/sjsf/icons.js';
1213
import { encodeJson } from '$lib/url.js';
1314
1415
import { PreviewSubRouteName, RouteName } from '../model.js';
@@ -34,6 +35,16 @@
3435
labels={THEME_TITLES}
3536
/>
3637
</div>
38+
<div class="flex flex-col gap-1.5">
39+
<Label for={`${uniqueId}-icons`}>Icons</Label>
40+
<Select
41+
class="w-full"
42+
labelId="{uniqueId}-icons"
43+
bind:value={ctx.icons}
44+
items={ICONS}
45+
labels={ICONS_TITLES}
46+
/>
47+
</div>
3748
<div class="flex items-center gap-2">
3849
<Checkbox id={`${uniqueId}-live`} bind:checked={ctx.livePreview} />
3950
<Label class="text-base" for={`${uniqueId}-live`}>Live preview</Label>

apps/builder/src/builder/preview/content.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<script lang="ts">
22
import { isRecordEmpty } from '@sjsf/form/lib/object';
33
4-
import Code, { type CodeFile } from '$lib/components/code.svelte';
5-
64
import DeviconPlainSvelte from '~icons/devicon-plain/svelte';
75
import DeviconPlainTypescript from '~icons/devicon-plain/typescript';
86
import DeviconPlainBash from '~icons/devicon-plain/bash';
97
import DeviconPlainCss from '~icons/devicon-plain/css';
108
import MdiCodeJson from '~icons/mdi/code-json';
119
10+
import Code, { type CodeFile } from '$lib/components/code.svelte';
11+
1212
import { getBuilderContext } from '../context.svelte.js';
1313
import { PreviewSubRouteName, type PreviewRoute } from '../model.js';
1414
import Form from './form.svelte';

apps/builder/src/builder/preview/preview.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<PreviewControls />
1515
</div>
1616

17-
<div class="pb-4 overflow-x-hidden">
17+
<div class="pb-4">
1818
<PreviewContent {route} />
1919
</div>
2020

apps/builder/src/lib/components/code.svelte

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,30 @@
3131
let selected = $derived(files[selectedIndex]);
3232
</script>
3333

34-
<div class="rounded-md border">
34+
<div>
3535
{#if selected}
36-
<div class="flex gap-2 border-b p-2">
37-
{#each files as file, i (file.title)}
38-
<Button
39-
size="sm"
40-
variant="ghost"
41-
class={[selectedIndex === i && 'bg-accent text-accent-foreground dark:bg-accent/50']}
42-
onclick={() => {
43-
selectedIndex = i;
44-
}}
45-
>
46-
<file.Icon />
47-
{file.title}</Button
48-
>
49-
{/each}
50-
<CopyButton class="ml-auto" size="sm" variant="ghost" text={() => selected.content} />
36+
<div class="sticky top-(--header-height) bg-background">
37+
<div class="flex gap-2 rounded-t-md border p-2">
38+
{#each files as file, i (file.title)}
39+
<Button
40+
size="sm"
41+
variant="ghost"
42+
class={[selectedIndex === i && 'bg-accent text-accent-foreground dark:bg-accent/50']}
43+
onclick={() => {
44+
selectedIndex = i;
45+
}}
46+
>
47+
<file.Icon />
48+
{file.title}</Button
49+
>
50+
{/each}
51+
<CopyButton class="ml-auto" size="sm" variant="ghost" text={() => selected.content} />
52+
</div>
5153
</div>
52-
<div>
54+
<div class="rounded-b-md border-x border-b">
5355
{@html selected.content}
5456
</div>
5557
{:else}
56-
<p class="p-4 text-center text-muted-foreground">No files</p>
58+
<p class="rounded-md border p-4 text-center text-muted-foreground">No files</p>
5759
{/if}
5860
</div>

0 commit comments

Comments
 (0)