Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Isokaeder authored and WIP committed Oct 3, 2024
1 parent 1637c03 commit ac2abe7
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 27 deletions.
4 changes: 2 additions & 2 deletions packages/documentation/components/CodePreview.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div :class="[$style.wrapper, $style[`wrapper--is-type-${type}`]]">
<section v-if="$slots.example" :class="$style.example">
<section v-if="$slots.default" :class="$style.example">
<slot />
</section>
<div v-else style="margin-bottom: -1px" />
Expand Down Expand Up @@ -37,7 +37,7 @@ export default defineComponent({
type: String as PropType<'default' | 'preview'>,
},
},
setup(props) {
setup(props, { slots }) {
const codeHtml = ref<string | null>(null)
watch(
Expand Down
233 changes: 211 additions & 22 deletions packages/documentation/components/component-form/ComponentForm.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
<script lang="ts">
import copy from 'copy-to-clipboard'
import { codeToHtml } from 'shiki'
import isEqual from 'lodash/isEqual.js'
import type { PropType } from 'vue'
import { defineComponent, ref, watch } from 'vue'
import { computed, defineComponent, ref, watch } from 'vue'
import { KtI18nContext } from '@3yourmind/kotti-ui'
type Component = {
name: string
props?: Record<
string,
| {
default: undefined
required: true
type: PropType<unknown>
}
| {
default: unknown | (() => unknown)
required: undefined | false
type: PropType<unknown>
}
>
}
type PropFormatter = (value: unknown) => string[]
type Slot = {
content: string
name: string
}
const generateCode = ({
component,
propFormatters,
props,
slots,
}: {
component: Component
propFormatters: Record<string, PropFormatter>
props: Record<string, unknown>
slots: Slot[]
}): string =>
[
`<${component.name}`,
...Object.entries(props)
.filter(([key, value]) => {
const _defaultValue = component.props?.[key]?.default
const defaultValue =
typeof _defaultValue === 'function' ? _defaultValue() : _defaultValue
return !isEqual(value, defaultValue)
})
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => {
if (value === true) return `\t${key}`
if (!(key in propFormatters))
return `\t${typeof value === 'string' ? '' : ':'}${key}="${value}"`
const lines = propFormatters[key](value)
if (lines.length < 2) return `\t:${key}="${lines.join('\n')}"`
const firstLine = lines.at(0)
const lastLine = lines.at(-1)
const otherLines = lines.slice(1, -1)
return [
`\t:${key}="${firstLine}`,
...otherLines.map((line) => `\t${line}`),
`\t${lastLine}"`,
].join('\n')
}),
`>`,
...slots.flatMap((slot) => {
if (slot.name === 'default') return [`\t${slot.content}`]
return [
`\t<template #${slot.name}>`,
`\t\t${slot.content}`,
'\t</template>',
]
}),
`</${component.name}>`,
].join('\n')
export default defineComponent({
name: 'ComponentForm',
components: {
Expand All @@ -12,25 +93,54 @@ export default defineComponent({
props: {
component: {
required: true,
type: Object as PropType<{
name: string
props?: Record<string, unknown>
}>,
type: Object as PropType<Component>,
},
componentProps: {
required: true,
hiddenProps: {
default: () => ({}),
type: Object as PropType<Record<string, unknown>>,
},
propFormatters: {
default: () => ({}),
type: Object as PropType<Record<string, PropFormatter>>,
},
props: {
default: () => ({}),
type: Object as PropType<Record<string, unknown>>,
},
slots: {
default: () => [],
type: Array as PropType<Slot[]>,
},
},
setup() {
const codeHtml = ref<string | null>(null)
setup(props, { slots }) {
const allSlots = computed(() => {
const result: Slot[] = [...props.slots]
for (const slotName of Object.keys(slots)) {
if (slotName === 'component-form-settings') continue
if (props.slots.some((slot) => slot.name === slotName)) continue
result.push({ name: slotName, content: `${slotName} content` })
}
return result
})
const code = computed(() =>
generateCode({
component: props.component,
propFormatters: props.propFormatters,
props: props.props,
slots: allSlots.value,
}),
)
const codeHtml = ref<string | null>(null)
watch(
() => null,
async () => {
const code =
'<VueComponent v-if="false" :label="label">\n\tcontent\n</VueComponent>'
codeHtml.value = await codeToHtml(code, {
code,
async (newCode) => {
codeHtml.value = await codeToHtml(newCode, {
lang: 'vue-html',
theme: 'vitesse-light',
})
Expand All @@ -39,29 +149,108 @@ export default defineComponent({
)
return {
allSlots,
codeHtml,
onCopy: () => {
copy(code.value)
},
}
},
})
</script>

<template>
<section>
<div class="example-preview">example preview</div>
<div class="code-preview">
<div :class="$style.code" v-html="codeHtml" />
<section :class="$style.wrapper">
<div :class="$style.preview">
<component :is="component" v-bind="{ ...props, ...hiddenProps }">
<template v-for="slot in allSlots" #[slot.name] :key="slot.name">
<component :is="$slots[slot.name]" v-if="$slots[slot.name]" />
<div v-else v-text="slot.content" />
</template>
</component>
</div>
<div :class="$style.settings">
<slot name="component-form-settings" />
</div>
<div :class="$style.actions">
<div :class="$style.language" v-text="'vue-html'" />
<div :class="$style.copyButton" role="button" @click="onCopy">
<i class="yoco">copy</i>
</div>
</div>
<div :class="$style.code" v-html="codeHtml" />
</section>
<div class="component-form" />
</template>

<style lang="scss" module>
.code {
padding: var(--unit-3) var(--unit-6);
> * {
background-color: var(--gray-10) !important;
padding: var(--unit-3) var(--unit-6);
margin: 0;
background-color: var(--gray-10) !important;
}
}
.actions {
display: flex;
align-items: center;
gap: var(--unit-6);
border: 1px solid transparent;
border-bottom-color: var(--gray-20);
background-color: var(--gray-10);
padding: var(--unit-3) var(--unit-6);
}
.wrapper {
border-radius: var(--border-radius);
border: 1px solid transparent;
overflow: hidden;
margin-bottom: var(--unit-8);
border-color: var(--gray-20);
}
.preview {
padding: var(--unit-3) var(--unit-6);
border-bottom: 1px solid var(--gray-20);
/* background-color: var(--gray-10); */
}
.settings {
display: flex;
border-bottom: 1px solid var(--gray-20);
background-color: #fcfcfc;
> div {
padding: var(--unit-3) var(--unit-6);
flex: 1;
flex-basis: 0;
&:not(:last-child) {
border-right: 1px solid var(--gray-20);
}
}
}
.copyButton {
display: flex;
padding: var(--unit-2);
margin: calc(-1 * var(--unit-2));
margin-left: auto; /* push to end of flexbox */
font-size: 1.2rem;
cursor: pointer;
&:hover {
color: var(--interactive-01);
}
}
.language {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
'Courier New', monospace;
}
</style>
33 changes: 31 additions & 2 deletions packages/documentation/pages/usage/components/accordion/+Page.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
<template>
<ComponentInfo v-bind="{ component }" />

<ComponentForm :component="component" :componentProps="{}" />
<ComponentForm
:component="component"
:propFormatters="{}"
:props="omit(componentProps, ['content'])"
:hiddenProps="{
'onUpdate:isClosed': (val: boolean) => (componentProps.isClosed = val),
}"
:slots="[{ content: componentProps.content, name: 'default' }]"
>
<template #component-form-settings>
<div>
<KtForm v-model:value="componentProps" size="small">
<KtFieldText label="title" formKey="title" />
<KtFieldText label="content" formKey="content" />
</KtForm>
</div>
</template>
</ComponentForm>

<h2>Basic Usage</h2>

Expand Down Expand Up @@ -120,8 +137,9 @@
</template>

<script lang="ts">
import { KtAccordion, KtButton } from '@3yourmind/kotti-ui'
import { KtAccordion, KtButton, KtFieldText, KtForm } from '@3yourmind/kotti-ui'
import { defineComponent, ref } from 'vue'
import omit from 'lodash/omit.js'
import ComponentForm from '~/components/component-form/ComponentForm.vue'
import CodePreview from '~/components/CodePreview.vue'
Expand All @@ -135,13 +153,24 @@ export default defineComponent({
ComponentInfo,
KtAccordion,
KtButton,
KtFieldText,
KtForm,
},
setup() {
return {
component: KtAccordion,
componentProps: ref({
dataTest: null,
icon: null,
isClosed: false,
content: 'Example Content',
title: 'Example title',
}),
isClosed: ref(false),
isFirstAccordionClosed: ref(false),
isSecondAccordionClosed: ref(false),
isThirdAccordionClosed: ref(false),
omit,
}
},
})
Expand Down
2 changes: 1 addition & 1 deletion packages/kotti-ui/source/kotti-accordion/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod'

export module KottiAccordion {
export const propsSchema = z.object({
dataTest: z.string().optional(),
dataTest: z.string().nullable().default(null),
icon: yocoIconSchema.nullable().default(null),
isClosed: z.boolean().default(false),
title: z.string(),
Expand Down

0 comments on commit ac2abe7

Please sign in to comment.