Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions devtools/app/components/ComponentCode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const { data: formattedCode } = useAsyncData('__compodium-component-formatted-co
}, { watch: [code] })

const { codeToHtml } = useShiki()

const { data: highlightedCode } = useAsyncData('__compodium-component-highlighted-code', async () => {
return formattedCode.value
? codeToHtml(formattedCode.value, 'vue')
Expand Down
131 changes: 92 additions & 39 deletions devtools/app/components/ComponentPropInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ObjectInput from '../components/inputs/ObjectInput.vue'
import ArrayInput from '../components/inputs/ArrayInput.vue'
import PrimitiveArrayInput from '../components/inputs/PrimitiveArrayInput.vue'
import DateInput from '../components/inputs/DateInput.vue'
import { createReusableTemplate } from '@vueuse/core'

const inputTypes: Record<PropInputType, Component> = {
array: ArrayInput,
Expand All @@ -22,7 +23,7 @@ const inputTypes: Record<PropInputType, Component> = {
</script>

<script setup lang="ts">
const props = defineProps<{ name?: string, schema: PropSchema[], description?: string, disabled?: boolean }>()
const props = defineProps<{ name?: string, schema: PropSchema[], description?: string, disabled?: boolean, inline?: boolean }>()
const modelValue = defineModel<any>()

const currentType = ref()
Expand Down Expand Up @@ -55,52 +56,104 @@ function inferDefaultInput(value?: any, types?: PropSchema[]): PropSchema | unde
const description = computed(() => {
return props.description?.replace(/`([^`]+)`/g, '<code class="text-xs font-medium bg-[var(--ui-bg-elevated)] px-1 py-0.5 rounded">$1</code>')
})

const isArray = computed(() => currentInput.value?.inputType === 'array')

const [DefineSelect, ReuseSelect] = createReusableTemplate()
const [DefineInput, ReuseInput] = createReusableTemplate()
const [DefineLabel, ReuseLabel] = createReusableTemplate()
const [DefineDescription, ReuseDescription] = createReusableTemplate()
</script>

<template>
<div class="relative">
<UFormField
:name="name"
class="w-full"
:ui="{ wrapper: 'mb-2' }"
:class="{ 'opacity-70 cursor-not-allowed': disabled || !currentInput }"
>
<template #label>
<div>
<p
v-if="name"
class="font-mono font-bold px-1.5 py-0.5 border border-[var(--ui-border-accented)] border-dashed rounded bg-[var(--ui-bg-elevated)]"
>
{{ name }}
</p>
</div>
</template>

<template #description>
<!-- eslint-disable vue/no-v-html -->
<p
v-if="description"
class="text-neutral-600 dark:text-neutral-400 mt-1"
v-html="description"
/>
</template>
<div class="w-full">
<DefineSelect>
<USelect
v-if="schema?.length > 1"
v-model="currentType"
variant="none"
size="sm"
:items="schema"
label-key="type"
value-key="type"
:trailing-icon="undefined"
class="font-medium text-ellipsis truncate max-w-50 py-0.5 px-1.5 font-mono bg-(--ui-bg-elevated)/50 border border-(--ui-border)"
@update:model-value="modelValue = undefined"
/>
</DefineSelect>

<DefineInput>
<component
:is="currentInput.component"
v-if="!disabled && currentInput"
v-model="modelValue"
class="w-full"
:schema="currentInput.schema"
:name="name"
/>
</DefineInput>

<DefineLabel>
<p class="truncate text-ellipsis font-mono font-semibold">
{{ name }}
</p>
</DefineLabel>
<DefineDescription>
<!-- eslint-disable vue/no-v-html -->
<p
v-if="description"
class="text-(--ui-text-muted) text-sm"
v-html="description"
/>
</UFormField>

<USelect
v-if="schema?.length > 1"
v-model="currentType"
variant="none"
:items="schema"
label-key="type"
value-key="type"
class="absolute top-2 right-5 max-w-xs font-mono"
@update:model-value="modelValue = undefined"
/>
</DefineDescription>

<template v-if="!inline || isArray">
<UFormField
:name="name"
class=""
:class="{ 'opacity-70 cursor-not-allowed': disabled || !currentInput }"
:label="name"
:ui="{ label: 'w-full flex gap-2 justify-between mb-1', description: 'mb-2', container: 'w-full', wrapper: name || schema?.length > 1 ? '' : 'hidden' }"
>
<template #label>
<ReuseLabel class="py-0.5" />
<ReuseSelect />
</template>

<ReuseDescription />

<div class="flex gap-2 justify-center">
<ReuseInput />
<slot name="actions" />
</div>
</UFormField>
</template>

<template v-else>
<div class="flex justify-end mb-2">
<ReuseSelect />
</div>
<UFormField
:name="name"
class="w-full flex gap-4"
:class="{ 'opacity-70 cursor-not-allowed': disabled || !currentInput }"
:label="name"
:ui="{ label: 'font-sans my-auto w-32', description: 'mb-2', container: 'mt-0 w-full', wrapper: name ? '' : 'hidden' }"
>
<template #label>
<ReuseLabel
v-if="name"
class="mt-1.5"
/>
</template>
<div class="flex w-full gap-2">
<ReuseInput />
<slot name="actions" />
</div>
</UFormField>
<div class="mt-2">
<ReuseDescription />
</div>
</template>
</div>
</template>
156 changes: 156 additions & 0 deletions devtools/app/components/JsonEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<script setup lang="ts">
import JsonEditorVue from 'json-editor-vue'
import { Mode } from 'vanilla-jsoneditor'

const modelValue = defineModel<Record<string, any>>({ default: () => {} })

const jsonValue = shallowRef(modelValue.value)

watch(jsonValue, (value) => {
if (value) modelValue.value = value
})
</script>

<template>
<div class="json-editor">
<JsonEditorVue
ref="editor"
v-model="jsonValue"
class="h-full w-full"
:mode="Mode.text"
:stringified="false"
:main-menu-bar="false"
:status-bar="false"
:indentation="2"
:tab-size="2"
/>
</div>
</template>

<style>
.json-editor {
--jse-font-familly: var(--font-sans);
--jse-font-familly-mono: var(--font-mono);

--jse-error-color: var(--ui-error);
--jse-warning-color: var(--ui-warning);
--jse-info-color: var(--ui-info);

/* over all fonts, sizes, and colors */
--jse-theme-color: var(--ui-primary);
--jse-theme-color-highlight: var(--ui-primary);
--jse-background-color: var(--ui-bg);
--jse-text-color: var(--ui-text);
--jse-text-color-inverse: var(--ui-text-inverted);

/* main, menu, modal */
--jse-main-border: 0;
--jse-menu-color: var(--ui-bg-inverted);
--jse-modal-background: var(--ui-bg-muted);
--jse-modal-overlay-background: rgba(0, 0, 0, 0.5);
--jse-modal-code-background: var(--ui-bg-muted);

/* tooltip in text mode */
--jse-tooltip-color: var(--jse-text-color);
--jse-tooltip-background: var(--ui-bg-accented);
--jse-tooltip-border: 1px solid var(--ui-border-accented);
--jse-tooltip-action-button-color: inherit;
--jse-tooltip-action-button-background: var(--ui-border-accented);

/* panels: navigation bar, gutter, search box */
--jse-panel-background: var(--ui-bg-muted);
--jse-panel-background-border: 1px solid var(--ui-border);
--jse-panel-color: var(--jse-text-color);
--jse-panel-color-readonly: var(--ui-text-dimmed);
--jse-panel-border: 1px solid var(--ui-border);
--jse-panel-button-color-highlight: var(--ui-bg-inverted);
--jse-panel-button-background-highlight: var(--ui-border);

/* navigation-bar */
--jse-navigation-bar-background: var(--ui-bg-accented);
--jse-navigation-bar-background-highlight: var(--ui-bg-elevated);
--jse-navigation-bar-dropdown-color: var(--jse-text-color);

/* context menu */
--jse-context-menu-background: var(--ui-bg-accented);
--jse-context-menu-background-highlight: var(--ui-bg-elevated);
--jse-context-menu-separator-color: var(--ui-border);
--jse-context-menu-color: var(--jse-text-color);
--jse-context-menu-pointer-background: var(--ui-border);
--jse-context-menu-pointer-background-highlight: var(--ui-border-accented);
--jse-context-menu-pointer-color: var(--jse-context-menu-color);

/* contents: json key and values */
--jse-key-color: var(--ui-text-toned);
--jse-value-color: var(--jse-text-color);
--jse-value-color-number: var(--ui-text-dimmed);
--jse-value-color-boolean: var(--ui-text-dimmed);
--jse-value-color-null: var(--ui-text-dimmed);
--jse-value-color-string: var(--ui-text-dimmed);
--jse-value-color-url: var(--ui-text-dimmed);
--jse-delimiter-color: var(--ui-text-dimmed);
--jse-edit-outline: 2px solid var(--jse-text-color);

/* contents: selected or hovered */
--jse-selection-background-color: var(--ui-bg-accented);
--jse-selection-background-inactive-color: var(--ui-bg-muted);
--jse-hover-background-color: var(--ui-bg-muted);
--jse-active-line-background-color: rgba(255, 255, 255, 0.06);
--jse-search-match-background-color: var(--ui-bg-muted);

/* contents: section of collapsed items in an array */
--jse-collapsed-items-background-color: var(--ui-bg-muted);
--jse-collapsed-items-selected-background-color: var(--ui-bg-accented);
--jse-collapsed-items-link-color: var(--ui-text-dimmed);
--jse-collapsed-items-link-color-highlight: var(--ui-text-toned);

/* contents: highlighting of search results */
--jse-search-match-color: var(--ui-text-toned);
--jse-search-match-outline: 1px solid var(--ui-text-toned);
--jse-search-match-active-color: var(--ui-text-highlighted);
--jse-search-match-active-outline: 1px solid var(--ui-text-highlighted);

/* contents: inline tags inside the JSON document */
--jse-tag-background: var(--ui-bg-accented);
--jse-tag-color: var(--ui-text-dimmed);

/* contents: table */
--jse-table-header-background: var(--ui-bg-muted);
--jse-table-header-background-highlight: var(--ui-bg-accented);
--jse-table-row-odd-background: rgba(255, 255, 255, 0.1);

/* controls in modals: inputs, buttons, and `a` */
--jse-input-background: var(--ui-bg-accented);
--jse-input-border: var(--jse-main-border);
--jse-button-background: var(--ui-border);
--jse-button-background-highlight: var(--ui-border-accented);
--jse-button-color: var(--ui-bg-inverted);
--jse-button-secondary-background: var(--ui-bg-accented);
--jse-button-secondary-background-highlight: var(--ui-bg-elevated);
--jse-button-secondary-background-disabled: var(--ui-border);
--jse-button-secondary-color: var(--jse-text-color);
--jse-a-color: var(--ui-text-toned);
--jse-a-color-highlight: var(--ui-text-highlighted);

/* svelte-select */
--jse-svelte-select-background: var(--ui-bg-accented);
--jse-svelte-select-border: 1px solid var(--ui-border);
--list-background: var(--ui-bg-accented);
--item-hover-bg: var(--ui-bg-elevated);
--multi-item-bg: var(--ui-bg-accented);
--input-color: var(--ui-text-dimmed);
--multi-clear-bg: var(--ui-border);
--multi-item-clear-icon-color: var(--ui-text-dimmed);
--multi-item-outline: 1px solid var(--ui-border);
--list-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.4);

/* color picker */
--jse-color-picker-background: var(--ui-bg-accented);
--jse-color-picker-border-box-shadow: var(--ui-border) 0 0 0 1px;
}

.json-editor .cm-gutter-lint {
visibility: hidden;
width: 0px;
}
</style>
33 changes: 19 additions & 14 deletions devtools/app/components/inputs/ArrayInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ArrayInputSchema } from '#module/runtime/server/services/infer'

defineProps<{ schema: ArrayInputSchema }>()

const modelValue = defineModel<Array<any>>({})
const modelValue = defineModel<Array<any>>()

function removeArrayItem(index: number) {
if (!modelValue.value) return
Expand All @@ -16,44 +16,49 @@ function addArrayItem() {
} else {
modelValue.value.push(null)
}
openedItem.value = modelValue.value?.length - 1
}

function updateValue(index: number, value: any) {
if (!modelValue.value) return
modelValue.value[index] = value
modelValue.value = [...modelValue.value]
}

const openedItem = ref<number | null>(0)
</script>

<template>
<div>
<div class="flex flex-col gap-2">
<div
v-for="value, index in modelValue"
:key="index"
class="relative"
class="relative w-full flex gap-2"
>
<ComponentPropInput
:model-value="value"
:schema="schema.schema"
@update:model-value="(val) => updateValue(index, val)"
/>

<UButton
variant="link"
color="neutral"
size="sm"
icon="lucide:x"
class="absolute top-2.5 right-1"
@click="removeArrayItem(index)"
/>
>
<template #actions>
<UButton
variant="outline"
color="neutral"
icon="i-lucide-x"
size="sm"
class="p-2"
square
@click="removeArrayItem(index)"
/>
</template>
</ComponentPropInput>
</div>

<UButton
icon="i-lucide-plus"
color="neutral"
variant="ghost"
block
class="justify-center mt-4"
@click="addArrayItem()"
>
Add value
Expand Down
Loading