diff --git a/.gitignore b/.gitignore index 8b3285e00..a409e24fd 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,6 @@ docs/content/docs/components/*.md coverage .nyc_output -cypress/screenshots \ No newline at end of file +cypress/screenshots + +.tmp-screenshots diff --git a/components.d.ts b/components.d.ts index ae3256f80..2f855fa3d 100644 --- a/components.d.ts +++ b/components.d.ts @@ -62,6 +62,7 @@ declare module 'vue' { FormLabel: typeof import('./src/components/FormLabel.vue')['default'] FrappeUIProvider: typeof import('./src/components/Provider/FrappeUIProvider.vue')['default'] FunnelChart: typeof import('./src/components/Charts/FunnelChart.vue')['default'] + Icon: typeof import('./src/components/Icon/Icon.vue')['default'] IframeNodeView: typeof import('./src/components/TextEditor/extensions/iframe/IframeNodeView.vue')['default'] ImageGroupNodeView: typeof import('./src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue')['default'] ImageGroupUploadDialog: typeof import('./src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue')['default'] @@ -109,6 +110,7 @@ declare module 'vue' { OptionIcon: typeof import('./src/components/shared/selection/OptionIcon.vue')['default'] Password: typeof import('./src/components/Password/Password.vue')['default'] PickerShell: typeof import('./src/components/shared/picker/PickerShell.vue')['default'] + Pill: typeof import('./src/components/Pill/Pill.vue')['default'] Popover: typeof import('./src/components/Popover/Popover.vue')['default'] Progress: typeof import('./src/components/Progress/Progress.vue')['default'] Rating: typeof import('./src/components/Rating/Rating.vue')['default'] @@ -129,7 +131,6 @@ declare module 'vue' { SuggestionList: typeof import('./src/components/TextEditor/extensions/suggestion/SuggestionList.vue')['default'] Switch: typeof import('./src/components/Switch/Switch.vue')['default'] TabButtons: typeof import('./src/components/TabButtons/TabButtons.vue')['default'] - 'TabButtons.story': typeof import('./src/components/TabButtons/TabButtons.story.vue')['default'] Tabs: typeof import('./src/components/Tabs/Tabs.vue')['default'] Textarea: typeof import('./src/components/Textarea/Textarea.vue')['default'] TextEditor: typeof import('./src/components/TextEditor/TextEditor.vue')['default'] diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 254455a8b..8be13d9c3 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -60,7 +60,10 @@ export default defineConfig({ dark: 'tokyo-night', light: 'github-light', }, - codeTransformers: [toClass], + // transformerStyleToClass flushes its CSS only at buildEnd, so in dev + // the generated shiki.css stays empty and code blocks lose colors — + // skip it in dev and let shiki emit inline --shiki-light/dark styles. + codeTransformers: isDev ? [] : [toClass], config(md) { md.use(componentTransformer) }, diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 5d47ce4bf..d56e5e882 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,8 +1,33 @@ import type { Theme } from 'vitepress' -import "../../../src/fonts/Inter/inter.css" -import "../../css/style.css" -import "../../css/shiki.css" +import '../../../src/fonts/Inter/inter.css' +import '../../css/style.css' +import '../../css/shiki.css' import Demo from '../../components/Docs/Demo.vue' +import ButtonBuilder from '../../components/Docs/ButtonBuilder.vue' +import BadgeBuilder from '../../components/Docs/BadgeBuilder.vue' +import PillBuilder from '../../components/Docs/PillBuilder.vue' +import TabButtonsBuilder from '../../components/Docs/TabButtonsBuilder.vue' +import AlertBuilder from '../../components/Docs/AlertBuilder.vue' +import AvatarBuilder from '../../components/Docs/AvatarBuilder.vue' +import BreadcrumbsBuilder from '../../components/Docs/BreadcrumbsBuilder.vue' +import CheckboxBuilder from '../../components/Docs/CheckboxBuilder.vue' +import ComboboxBuilder from '../../components/Docs/ComboboxBuilder.vue' +import DialogBuilder from '../../components/Docs/DialogBuilder.vue' +import DividerBuilder from '../../components/Docs/DividerBuilder.vue' +import DropdownBuilder from '../../components/Docs/DropdownBuilder.vue' +import ErrorMessageBuilder from '../../components/Docs/ErrorMessageBuilder.vue' +import FormControlBuilder from '../../components/Docs/FormControlBuilder.vue' +import MultiSelectBuilder from '../../components/Docs/MultiSelectBuilder.vue' +import PasswordBuilder from '../../components/Docs/PasswordBuilder.vue' +import ProgressBuilder from '../../components/Docs/ProgressBuilder.vue' +import RatingBuilder from '../../components/Docs/RatingBuilder.vue' +import SelectBuilder from '../../components/Docs/SelectBuilder.vue' +import SliderBuilder from '../../components/Docs/SliderBuilder.vue' +import SwitchBuilder from '../../components/Docs/SwitchBuilder.vue' +import TabsBuilder from '../../components/Docs/TabsBuilder.vue' +import TextInputBuilder from '../../components/Docs/TextInputBuilder.vue' +import TextareaBuilder from '../../components/Docs/TextareaBuilder.vue' +import TooltipBuilder from '../../components/Docs/TooltipBuilder.vue' import Layout from '../../components/Layout.vue' if (process.env.NODE_ENV === 'production') { @@ -10,8 +35,33 @@ if (process.env.NODE_ENV === 'production') { } export default { - Layout, + Layout, enhanceApp({ app, router, siteData }) { app.component('ComponentPreview', Demo) + app.component('ButtonBuilder', ButtonBuilder) + app.component('BadgeBuilder', BadgeBuilder) + app.component('PillBuilder', PillBuilder) + app.component('TabButtonsBuilder', TabButtonsBuilder) + app.component('AlertBuilder', AlertBuilder) + app.component('AvatarBuilder', AvatarBuilder) + app.component('BreadcrumbsBuilder', BreadcrumbsBuilder) + app.component('CheckboxBuilder', CheckboxBuilder) + app.component('ComboboxBuilder', ComboboxBuilder) + app.component('DialogBuilder', DialogBuilder) + app.component('DividerBuilder', DividerBuilder) + app.component('DropdownBuilder', DropdownBuilder) + app.component('ErrorMessageBuilder', ErrorMessageBuilder) + app.component('FormControlBuilder', FormControlBuilder) + app.component('MultiSelectBuilder', MultiSelectBuilder) + app.component('PasswordBuilder', PasswordBuilder) + app.component('ProgressBuilder', ProgressBuilder) + app.component('RatingBuilder', RatingBuilder) + app.component('SelectBuilder', SelectBuilder) + app.component('SliderBuilder', SliderBuilder) + app.component('SwitchBuilder', SwitchBuilder) + app.component('TabsBuilder', TabsBuilder) + app.component('TextInputBuilder', TextInputBuilder) + app.component('TextareaBuilder', TextareaBuilder) + app.component('TooltipBuilder', TooltipBuilder) }, } satisfies Theme diff --git a/docs/components/Docs/AlertBuilder.vue b/docs/components/Docs/AlertBuilder.vue new file mode 100644 index 000000000..f54e27897 --- /dev/null +++ b/docs/components/Docs/AlertBuilder.vue @@ -0,0 +1,61 @@ + + + diff --git a/docs/components/Docs/AvatarBuilder.vue b/docs/components/Docs/AvatarBuilder.vue new file mode 100644 index 000000000..dff2932d2 --- /dev/null +++ b/docs/components/Docs/AvatarBuilder.vue @@ -0,0 +1,53 @@ + + + diff --git a/docs/components/Docs/BadgeBuilder.vue b/docs/components/Docs/BadgeBuilder.vue new file mode 100644 index 000000000..68b1699e5 --- /dev/null +++ b/docs/components/Docs/BadgeBuilder.vue @@ -0,0 +1,79 @@ + + + diff --git a/docs/components/Docs/BreadcrumbsBuilder.vue b/docs/components/Docs/BreadcrumbsBuilder.vue new file mode 100644 index 000000000..98f560972 --- /dev/null +++ b/docs/components/Docs/BreadcrumbsBuilder.vue @@ -0,0 +1,48 @@ + + + diff --git a/docs/components/Docs/ButtonBuilder.vue b/docs/components/Docs/ButtonBuilder.vue new file mode 100644 index 000000000..050d08bf4 --- /dev/null +++ b/docs/components/Docs/ButtonBuilder.vue @@ -0,0 +1,95 @@ + + + diff --git a/docs/components/Docs/CheckboxBuilder.vue b/docs/components/Docs/CheckboxBuilder.vue new file mode 100644 index 000000000..0fffce79f --- /dev/null +++ b/docs/components/Docs/CheckboxBuilder.vue @@ -0,0 +1,49 @@ + + + diff --git a/docs/components/Docs/ComboboxBuilder.vue b/docs/components/Docs/ComboboxBuilder.vue new file mode 100644 index 000000000..273258a39 --- /dev/null +++ b/docs/components/Docs/ComboboxBuilder.vue @@ -0,0 +1,86 @@ + + + diff --git a/docs/components/Docs/ComponentPlayground.vue b/docs/components/Docs/ComponentPlayground.vue new file mode 100644 index 000000000..87637cd3f --- /dev/null +++ b/docs/components/Docs/ComponentPlayground.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/docs/components/Docs/Demo.vue b/docs/components/Docs/Demo.vue index 4685063f6..2ca5928e1 100644 --- a/docs/components/Docs/Demo.vue +++ b/docs/components/Docs/Demo.vue @@ -26,7 +26,7 @@ const isEditorDemo = computed(() => props.name?.startsWith('Editor'))
@@ -45,7 +45,7 @@ const isEditorDemo = computed(() => props.name?.startsWith('Editor'))
+import { ref } from 'vue' +import { Dialog, Button } from 'frappe-ui' +import ComponentPlayground, { type Knob } from './ComponentPlayground.vue' + +const open = ref(false) + +const knobs: Knob[] = [ + { name: 'title', type: 'text', default: 'Delete project', width: '14rem' }, + { + name: 'message', + type: 'text', + default: 'This will permanently remove the project. This action cannot be undone.', + width: '22rem', + }, + { + name: 'size', + type: 'tabs', + default: 'lg', + options: [ + { label: 'sm', value: 'sm' }, + { label: 'md', value: 'md' }, + { label: 'lg', value: 'lg' }, + { label: 'xl', value: 'xl' }, + { label: '2xl', value: '2xl' }, + ], + }, + { + name: 'position', + type: 'tabs', + default: 'center', + options: [ + { label: 'center', value: 'center' }, + { label: 'top', value: 'top' }, + ], + }, + { name: 'icon', type: 'switch', default: false }, + { name: 'actions', type: 'switch', default: true }, + { name: 'dismissible', type: 'switch', default: true }, + { name: 'showCloseButton', type: 'switch', default: true }, +] + +function buildCode(v: Record) { + const attrs: string[] = [`title="${v.title}"`, `message="${v.message}"`] + if (v.size !== 'lg') attrs.push(`size="${v.size}"`) + if (v.position !== 'center') attrs.push(`position="${v.position}"`) + if (v.icon) attrs.push(`:icon="{ name: 'lucide-trash-2', theme: 'red' }"`) + if (v.actions) { + attrs.push(`:actions="[ + { label: 'Cancel' }, + { label: 'Delete', variant: 'solid', theme: 'red' }, + ]"`) + } + if (!v.dismissible) attrs.push(':dismissible="false"') + if (!v.showCloseButton) attrs.push(':show-close-button="false"') + attrs.push('v-model:open="open"') + return [' ' ' + a), '/>'].join('\n') +} + +function actionsFor(enabled: boolean) { + if (!enabled) return undefined + return [ + { label: 'Cancel' }, + { label: 'Delete', variant: 'solid', theme: 'red' }, + ] as any +} + + + diff --git a/docs/components/Docs/DividerBuilder.vue b/docs/components/Docs/DividerBuilder.vue new file mode 100644 index 000000000..66516ff9b --- /dev/null +++ b/docs/components/Docs/DividerBuilder.vue @@ -0,0 +1,66 @@ + + + diff --git a/docs/components/Docs/DropdownBuilder.vue b/docs/components/Docs/DropdownBuilder.vue new file mode 100644 index 000000000..5c6d94c27 --- /dev/null +++ b/docs/components/Docs/DropdownBuilder.vue @@ -0,0 +1,57 @@ + + + diff --git a/docs/components/Docs/ErrorMessageBuilder.vue b/docs/components/Docs/ErrorMessageBuilder.vue new file mode 100644 index 000000000..6c3011f11 --- /dev/null +++ b/docs/components/Docs/ErrorMessageBuilder.vue @@ -0,0 +1,25 @@ + + + diff --git a/docs/components/Docs/FormControlBuilder.vue b/docs/components/Docs/FormControlBuilder.vue new file mode 100644 index 000000000..cfed3d458 --- /dev/null +++ b/docs/components/Docs/FormControlBuilder.vue @@ -0,0 +1,88 @@ + + + diff --git a/docs/components/Docs/MobileNavSheet.vue b/docs/components/Docs/MobileNavSheet.vue index eb43054f0..f83d97c8e 100644 --- a/docs/components/Docs/MobileNavSheet.vue +++ b/docs/components/Docs/MobileNavSheet.vue @@ -91,7 +91,7 @@ onUnmounted(() => { >