Skip to content

Commit

Permalink
More playground examples
Browse files Browse the repository at this point in the history
  • Loading branch information
xinjie-zhang committed Jan 8, 2023
1 parent e3ef1ec commit 1477717
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 209 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,43 @@
# Vimesh Headless UI
# Vimesh Headless UI
This is Alpine.js implementation of [Tailwind Headless UI](https://headlessui.com/). Built with [Vimesh UI](https://github.com/vimeshjs/vimesh-ui) framework, Vimesh Headless UI has some unique features:
## Ultra lightweight
Vimesh Headless UI has much less code size

| Component | Vimesh Headless UI | Tailwind Headless UI for Vue | Tailwind Headless UI for React |
| ----------- | -------------------- | -------- | ----- |
| Listbox | 8k | 34k | 30k |
| Combobox | 9k | 25k | 39k |
| Menu | 7k | 18k | 20k |
| Switch | 0.6k | 5k | 6k |
| Tabs | 4k | 12k | 16k |
| Dialog | 2k | 15k | 17k |
| Popover | 6k | 23k | 28k |
| Radio Group | 1k | 11k | 14k |

Comparing the production version of Vimesh and Tailwind headless dialog example page size, Vimesh is much smaller with more features and less bugs (check the menu display in the popup dialog).

| Vimesh | Tailwind |
| ---- | ---- |
| ![](./assets/vimesh001.png) | ![](./assets/tailwind001.png) |
| 192k | 425k |

## Load only used components dynamically
Components are plain html files, which could be hosted anywhere, normally at CDN. They could be shared cross different projects without extra tree shaking magic. For example, the dialog basic example uses two components `hui-dialog` and `hui-menu`. Just load them asynchronously with `x-import`, [Vimesh UI](https://github.com/vimeshjs/vimesh-ui) registers native custom elements and initialize them.

```html
<template x-component:page="dialog-basic" x-import="hui:dialog,menu" x-data="setupDialogBasicData()"
class="overflow-y-auto">
...
<hui-dialog :open="isOpen" @close="setIsOpen(false)">
...
<hui-menu>
</hui-menu>
</hui-dialog>
...
</template>
```

## No build, no bundle
What you write is what you get. Organize components to html files under meaningful namespaces. You do not need webpack, rollup, vite etc.

## Easy to debug
Binary file added assets/tailwind001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/vimesh001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 20 additions & 9 deletions components/combobox.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<template x-component:hui="combobox" x-modelable="value" x-data="{
value: null,
query: '',
get isOpen() { return this.$api && this.$api.isOpen },
get disabled() {return $prop('disabled')},
get multiple() {return $prop('multiple')}
get multiple() {return $prop('multiple')},
get activeOption() {
return this.$api && this.$api.activeOption
}
}">

<input type="hidden" :name="$prop('name')" :value="$prop('value', '')">
Expand All @@ -13,7 +17,10 @@
return {
_elActive: null,
_isOpen: false,

get activeOption() {
if (!this.$api._elActive) return null
return $vui.$api(this.$api._elActive).value
},
ensureActive() {
let elOptions = this.enabledOptions
if (elOptions.length > 0 && (!this._elActive || -1 === elOptions.indexOf(this._elActive)))
Expand Down Expand Up @@ -87,9 +94,13 @@
select(val) {
if (!this.multiple)
this.value = val
else if (_.isArray(this.value))
this.value.indexOf(val) === -1 && this.value.push(val)
else
else if (_.isArray(this.value)) {
let index = this.value.indexOf(val)
if (index === -1)
this.value.push(val)
else
this.value.splice(index, 1)
} else
this.value = [val]
},
selectActive() {
Expand Down Expand Up @@ -133,7 +144,7 @@
onSelect() {
if (!this.disabled) {
this.select()
this.close()
if (!this.context.multiple) this.close()
}
}
}
Expand Down Expand Up @@ -161,7 +172,7 @@
</template>
<template x-component:hui.unwrap="combobox-input">
<input tabindex="0" @keydown="$api.onKeydown($event)" @click="$api.ensureOpen()" @input.stop="$api.onInput()"
:placeholder="$api && $api.displayValue" />
:placeholder="$prop('placeholder') || $api && $api.displayValue" />
<script>
return {
get context() { return this.$of(':combobox') },
Expand All @@ -170,7 +181,7 @@
let val = this.context.value
if (displayValueFunc && val)
val = displayValueFunc(val)
return val || ''
return $vui._.isString(val) ? val : ''
},
onMounted() {
$vui.effect(() => this.syncInputValue())
Expand Down Expand Up @@ -209,7 +220,7 @@
e.preventDefault()
e.stopPropagation()
context.selectActive()
context.close()
if (!context.multiple) context.close()
this.$el.blur()
break;
case 'ArrowDown':
Expand Down
19 changes: 10 additions & 9 deletions playground/dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,24 @@
get isHome() { return this.$route.page === 'home' },
routes: [
{ name: 'Home', path: '/index', page: 'home' },
{ name: 'Listbox', path: '/listbox/basic', page: 'listbox/basic' },
{ name: 'Listbox Multi Select', path: '/listbox/multi-select', page: 'listbox/multi-select' },
{ name: 'Listbox Multi Elements', path: '/listbox/multi-elements', page: 'listbox/multi-elements' },
{ name: 'Radio Group', path: '/radio-group/basic', page: 'radio-group/basic' },
{ name: 'Switch', path: '/switch/basic', page: 'switch/basic' },
{ name: 'Tabs', path: '/tabs/basic', page: 'tabs/basic' },
{ name: 'Combobox', path: '/combobox/basic', page: 'combobox/basic' },
{ name: 'Combobox Multi Select', path: '/combobox/multi-select', page: 'combobox/multi-select' },
{ name: 'Combobox Command Palette', path: '/combobox/command-palette', page: 'combobox/command-palette' },
{ name: 'Combobox Command Palette with Group', path: '/combobox/command-palette-group', page: 'combobox/command-palette-group' },
{ name: 'Menu', path: '/menu/basic', page: 'menu/basic' },
{ name: 'Menu Multiple Elements', path: '/menu/multiple-elements', page: 'menu/multiple-elements' },
{ name: 'Menu with Popper', path: '/menu/with-popper', page: 'menu/with-popper' },
{ name: 'Menu with Transition', path: '/menu/with-transition', page: 'menu/with-transition' },
{ name: 'Menu with Transition and Popper', path: '/menu/with-transition-and-popper', page: 'menu/with-transition-and-popper' },

{ name: 'Popover', path: '/popover/basic', page: 'popover/basic' },
{ name: 'Dialog', path: '/dialog/basic', page: 'dialog/basic' },
{ name: 'Dialog with Slide Over', path: '/dialog/slide-over', page: 'dialog/slide-over' },

{ name: 'Switch', path: '/switch/basic', page: 'switch/basic' },
{ name: 'Tabs', path: '/tabs/basic', page: 'tabs/basic' },
{ name: 'Combobox', path: '/combobox/basic', page: 'combobox/basic' },
{ name: 'Listbox', path: '/listbox/basic', page: 'listbox/basic' },
{ name: 'Listbox Multi Select', path: '/listbox/multi-select', page: 'listbox/multi-select' },
{ name: 'Listbox Multi Elements', path: '/listbox/multi-elements', page: 'listbox/multi-elements' },
{ name: 'Radio Group', path: '/radio-group/basic', page: 'radio-group/basic' },
{ name: 'Something Wrong!', path: '(.*)', page: 'home', hidden: true }
]
}
Expand Down
21 changes: 11 additions & 10 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<script src="https://unpkg.com/@vimesh/style"></script>
<script src="https://unpkg.com/@vimesh/ui"></script>
<script src="https://unpkg.com/alpinejs" defer></script>
<script src="https://unpkg.com/universal-router/universal-router.min.js"></script>
<script src="https://unpkg.com/universal-router/universal-router.min.js"></script>

<script>
const DEBUG = false
Expand All @@ -19,23 +19,24 @@
get isHome() { return this.$route.page === 'home' },
routes: [
{ name: 'Home', path: '/index', page: 'home' },
{ name: 'Listbox', path: '/listbox/basic', page: 'listbox/basic' },
{ name: 'Listbox Multi Select', path: '/listbox/multi-select', page: 'listbox/multi-select' },
{ name: 'Listbox Multi Elements', path: '/listbox/multi-elements', page: 'listbox/multi-elements' },
{ name: 'Radio Group', path: '/radio-group/basic', page: 'radio-group/basic' },
{ name: 'Switch', path: '/switch/basic', page: 'switch/basic' },
{ name: 'Tabs', path: '/tabs/basic', page: 'tabs/basic' },
{ name: 'Combobox', path: '/combobox/basic', page: 'combobox/basic' },
{ name: 'Combobox Multi Select', path: '/combobox/multi-select', page: 'combobox/multi-select' },
{ name: 'Combobox Command Palette', path: '/combobox/command-palette', page: 'combobox/command-palette' },
{ name: 'Combobox Command Palette with Group', path: '/combobox/command-palette-group', page: 'combobox/command-palette-group' },
{ name: 'Menu', path: '/menu/basic', page: 'menu/basic' },
{ name: 'Menu Multiple Elements', path: '/menu/multiple-elements', page: 'menu/multiple-elements' },
{ name: 'Menu with Popper', path: '/menu/with-popper', page: 'menu/with-popper' },
{ name: 'Menu with Transition', path: '/menu/with-transition', page: 'menu/with-transition' },
{ name: 'Menu with Transition and Popper', path: '/menu/with-transition-and-popper', page: 'menu/with-transition-and-popper' },

{ name: 'Popover', path: '/popover/basic', page: 'popover/basic' },
{ name: 'Dialog', path: '/dialog/basic', page: 'dialog/basic' },
{ name: 'Dialog with Slide Over', path: '/dialog/slide-over', page: 'dialog/slide-over' },

{ name: 'Switch', path: '/switch/basic', page: 'switch/basic' },
{ name: 'Tabs', path: '/tabs/basic', page: 'tabs/basic' },
{ name: 'Combobox', path: '/combobox/basic', page: 'combobox/basic' },
{ name: 'Listbox', path: '/listbox/basic', page: 'listbox/basic' },
{ name: 'Listbox Multi Select', path: '/listbox/multi-select', page: 'listbox/multi-select' },
{ name: 'Listbox Multi Elements', path: '/listbox/multi-elements', page: 'listbox/multi-elements' },
{ name: 'Radio Group', path: '/radio-group/basic', page: 'radio-group/basic' },
{ name: 'Something Wrong!', path: '(.*)', page: 'home', hidden: true }
]
}
Expand Down
121 changes: 121 additions & 0 deletions playground/pages/combobox/command-palette-group.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script>

function setupComboboxCommandPaletteGroupData() {

let everybody = [
{ id: 1, img: 'https://github.com/adamwathan.png', name: 'Adam Wathan' },
{ id: 2, img: 'https://github.com/sschoger.png', name: 'Steve Schoger' },
{ id: 3, img: 'https://github.com/bradlc.png', name: 'Brad Cornes' },
{ id: 4, img: 'https://github.com/simonswiss.png', name: 'Simon Vrachliotis' },
{ id: 5, img: 'https://github.com/robinmalfait.png', name: 'Robin Malfait' },
{
id: 6,
img: 'https://pbs.twimg.com/profile_images/1478879681491394569/eV2PyCnm_400x400.jpg',
name: 'James McDonald',
},
{ id: 7, img: 'https://github.com/reinink.png', name: 'Jonathan Reinink' },
{ id: 8, img: 'https://github.com/thecrypticace.png', name: 'Jordan Pittman' },
]

return {
query: '',
activePerson: everybody[1],
get people() {
return this.query === ''
? everybody
: everybody.filter((person) =>
person.name.toLowerCase().includes(this.query.toLowerCase())
)
},

get groups() {
return this.people.reduce((groups, person) => {
let lastNameLetter = person.name.split(' ')[1][0]

groups.set(lastNameLetter, [...(groups.get(lastNameLetter) || []), person])

return groups
}, new Map())
},

get sortedGroups() {
return Array.from(this.groups.entries()).sort(([letterA], [letterZ]) =>
letterA.localeCompare(letterZ)
)
},

displayValue: () => (item) => item && item.name
}
}
</script>
<template x-component:page="combobox-command-palette-group" x-import="hui:combobox"
x-data="setupComboboxCommandPaletteGroupData()">
<div class=" flex h-full w-full justify-center bg-gray-50 p-12">
<div class="mx-auto w-full max-w-lg">
<div class="space-y-1">
<hui-combobox x-model="activePerson"
class="w-full overflow-hidden rounded border border-black/5 bg-white bg-clip-padding shadow-sm">
<div class="flex w-full flex-col">
<hui-combobox-input @change="query = $event.target.value"
class="w-full rounded-none border-none px-3 py-1 outline-none" placeholder="Search users…"
:display-value="displayValue"></hui-combobox-input>
<div class="flex">
<hui-combobox-options
class="shadow-xs max-h-60 flex-1 overflow-auto text-base leading-6 focus:outline-none sm:text-sm sm:leading-5">
<template x-for="[letter, people] of sortedGroups" :key="letter">
<div>
<div class="bg-gray-100 px-4 py-2" x-text="letter"></div>
<template x-for="person in people">
<hui-combobox-option :value="person">

<div :class="[
'relative flex cursor-default select-none space-x-4 py-2 pl-3 pr-9 focus:outline-none',
active ? 'bg-indigo-600 text-white' : 'text-gray-900',
]">
<img :src="person.img"
class="h-6 w-6 overflow-hidden rounded-full" />
<span
:class="['block truncate', selected ? 'font-semibold' : 'font-normal']"
x-text="person.name">
</span>
<span x-show="active" :class="[
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-indigo-600',
]">
<svg class="h-5 w-5" viewBox="0 0 25 24" fill="none">
<path d="M11.25 8.75L14.75 12L11.25 15.25"
stroke="currentColor" strokeWidth="1.5"
strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</div>
</hui-combobox-option>
</template>
</div>
</template>
</hui-combobox-options>
<template x-if="people.length === 0">
<div class="w-full py-4 text-center">
No person selected
</div>
</template>
<template x-if="people.length > 0 && isOpen && activeOption">
<div class="border-l">
<div class="flex flex-col">
<div class="p-8 text-center">
<img :src="activeOption.img"
class="mb-4 inline-block h-16 w-16 overflow-hidden rounded-full" />
<div class="font-bold text-gray-900" x-text="activeOption.name"></div>
<div class="text-gray-700">Obviously cool person</div>
</div>
</div>
</div>
</template>

</div>
</div>
</hui-combobox>
</div>
</div>
</div>
</template>
Loading

0 comments on commit 1477717

Please sign in to comment.