initial release

This commit is contained in:
khairulhaaziq 2023-07-04 04:29:47 +08:00
commit c297f5a24e
113 changed files with 5838 additions and 0 deletions

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
test-results/
playwright-report/
vite.config.ts.timestamp*

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 radix-vue
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# Shadcn UI Vue
Vue port of shadcn-ui.
This project is in active development by [radix vue](github.com/radix-vue/radix-vue) team. We are working on this while porting radix to vue, simultaneously.
We welcome contributors to both [radix vue](github.com/radix-vue/radix-vue) and [shadcn vue](github.com/radix-vue/shadcn-vue). We are actively looking someone that can lead the shadcn vue project based on radix-vue. Please [join our discord](https://discord.gg/dpf7BZY3) and we will get you up and running with the project, lets build them together!
If you want to get notified when we release the initial version, please give it a star! Hopefully we will get them done by this month!
Sincerely, radix-vue team.

24
packages/shadcn-vue/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View File

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,36 @@
{
"name": "radix-vue",
"version": "0.0.1",
"files": ["dist"],
"main": "./dist/radix-vue.umd.js",
"module": "./dist/radix-vue.es.js",
"exports": {
".": {
"import": "./dist/radix-vue.es.js",
"require": "./dist/radix-vue.umd.js"
},
"./dist/style.css": "./dist/style.css"
},
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@headlessui-float/vue": "^0.11.2",
"@headlessui/vue": "^1.7.14",
"@morev/vue-transitions": "^2.3.6",
"vue": "^3.2.47"
},
"devDependencies": {
"@iconify/vue": "^4.1.1",
"@vitejs/plugin-vue": "^4.1.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.3.9",
"vue-tsc": "^1.4.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,386 @@
<script setup lang="ts">
import { ref } from "vue";
import {
Accordion,
AccordionTrigger,
AccordionItem,
AccordionContent,
} from "./components/Accordion";
import { Alert, AlertDescription, AlertTitle } from "./components/Alert";
import { Card as InternalCard } from "./components/Internal/Docs/Card";
import { TransitionExpand } from "@morev/vue-transitions";
import { Icon } from "@iconify/vue";
import { Button } from "./components/Button";
import {
AlertDialog,
AlertDialogTitle,
AlertDialogHeader,
AlertDialogDescription,
AlertDialogFooter,
} from "./components/AlertDialog";
import {
Dialog,
DialogTitle,
DialogHeader,
DialogDescription,
DialogFooter,
} from "./components/Dialog";
import { Avatar, AvatarFallback, AvatarImage } from "./components/Avatar";
import { Badge } from "./components/Badge";
import { Checkbox } from "./components/Checkbox";
import { Label } from "./components/Label";
import { Input } from "./components/Input";
import { Textarea } from "./components/Textarea";
import { Switch } from "./components/Switch";
import { Toggle } from "./components/Toggle";
import { Card } from "./components/Demos";
import { AspectRatio, Image } from "./components/AspectRatio";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "./components/Collapsible";
import { Select, SelectContent, SelectItem, SelectLabel, SelectTrigger } from "./components/Select";
import { Popover, PopoverContent, PopoverTrigger } from "./components/Popover";
import { Separator } from "./components/Separator";
import { Tooltip, TooltipTrigger } from "./components/Tooltip";
import { RadioGroup, RadioGroupItem } from "./components/RadioGroup";
const alertDialogIsOpen = ref(false);
const dialogIsOpen = ref(false);
const enabled = ref(false);
const checkboxEnabled = ref(false);
const checkboxEnabled2 = ref(false);
const toggleEnabled = ref(false);
const people = [
{ id: 1, name: "Durward Reynolds", unavailable: false },
{ id: 2, name: "Kenton Towne", unavailable: false },
{ id: 3, name: "Therese Wunsch", unavailable: false },
{ id: 4, name: "Benedict Kessler", unavailable: true },
{ id: 5, name: "Katelyn Rohan", unavailable: false },
];
const selectedPerson = ref(people[0]);
const radioOptions = [
"Default",
"Comfortable",
"Compact",
];
const radioValue = ref(radioOptions[0]);
const components = [
"Accordion",
"Alert",
"Alert Dialog",
"Aspect Ratio",
"Avatar",
"Badge",
"Button",
"Calendar",
"Card",
"Checkbox",
"Collapsible",
"Combobox",
"Command",
"Context Menu",
"Data Table",
"Date Picker",
"Dialog",
"Dropdown Menu",
"Form",
"Hover Card",
"Input",
"Label",
"Menubar",
"Navigation Menu",
"Popover",
"Progress",
"Radio Group",
"Scroll Area",
"Select",
"Separator",
"Sheet",
"Skeleton",
"Slider",
"Switch",
"Table",
"Tabs",
"Textarea",
"Toast",
"Toggle",
"Tooltip",
];
</script>
<template>
<div class="flex flex-col items-center px-6 pb-6">
<div class="mt-32 mb-16 w-full max-w-6xl flex flex-col items-center">
<h1 class="text-7xl font-bold mb-2">shadcn vue</h1>
<p class="text-md text-neutral-700">
This is a description of easy UI that has been remade over 10000 times.
</p>
</div>
<div class="flex max-w-6xl">
<div class="w-[200px] pr-4">
<nav class="gap-0.5 flex flex-col text-sm sticky top-0 max-h-screen overflow-scroll">
<button v-for="(link, index) of components" :key="index"
:class="` group relative flex items-center gap-2.5 px-3 py-1.5 rounded-md text-gray-600 hover:text-black hover:bg-gray-200`">
<span :class="`truncate relative`">{{ link }}</span>
</button>
</nav>
</div>
<div class="flex-grow grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<InternalCard title="Accordion">
<div class="w-full max-w-[400px]">
<Accordion>
<AccordionItem>
<AccordionTrigger> Is it accessible? </AccordionTrigger>
<transition-expand>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</transition-expand>
</AccordionItem>
<AccordionItem>
<AccordionTrigger> Is it styled? </AccordionTrigger>
<transition-expand>
<AccordionContent>
Yes. It comes with default styles that matches the other components'
aesthetic.
</AccordionContent>
</transition-expand>
</AccordionItem>
<AccordionItem>
<AccordionTrigger> Is it animated? </AccordionTrigger>
<transition-expand>
<AccordionContent>
Yes. It's animated by default, but you can disable it if you prefer.
</AccordionContent>
</transition-expand>
</AccordionItem>
</Accordion>
</div>
</InternalCard>
<InternalCard title="Alert">
<div class="max-w-[400px]">
<Alert>
<Icon icon="lucide:terminal" class="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
You can add components to your app using the cli.
</AlertDescription>
</Alert>
</div>
</InternalCard>
<InternalCard title="Alert Dialog">
<Button label="Open" @click="alertDialogIsOpen = true">Show Dialog</Button>
<AlertDialog v-model="alertDialogIsOpen">
<AlertDialogHeader>
<AlertDialogTitle>Title</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter><Button variant="outline"
@click="alertDialogIsOpen = false">Cancel</Button><Button>CTA Button</Button>
</AlertDialogFooter>
</AlertDialog>
</InternalCard>
<InternalCard title="Aspect Ratio">
<AspectRatio :ratio="16 / 9" class="bg-muted">
<Image src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer" fill class="rounded-md object-cover" />
</AspectRatio>
</InternalCard>
<InternalCard title="Avatar">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</InternalCard>
<InternalCard title="Badge">
<Badge>Badge</Badge>
</InternalCard>
<InternalCard title="Button">
<Button>Button</Button>
</InternalCard>
<InternalCard title="Calendar"></InternalCard>
<InternalCard title="Card">
<Card />
</InternalCard>
<InternalCard title="Checkbox">
<div class="flex items-center space-x-2">
<Checkbox id="terms" v-model="checkboxEnabled" />
<label htmlFor="terms"
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
Accept terms and conditions
</label>
</div>
</InternalCard>
<InternalCard title="Collapsible">
<Collapsible class="w-[350px] space-y-2" v-slot="{ open }">
<div class="flex items-center justify-between space-x-4 px-4">
<h4 class="text-sm font-semibold">@peduarte starred 3 repositories</h4>
<CollapsibleTrigger>
<Button variant="outline" class="w-9 p-0 aspect-square">
<span>
<Icon v-show="!open" icon="lucide:chevrons-up-down" class="h-4 w-4 text-black" />
<Icon v-show="open" icon="lucide:x" class="h-4 w-4 text-black" />
</span>
</Button>
</CollapsibleTrigger>
</div>
<div class="rounded-md border px-4 py-3 font-mono text-sm">
@radix-ui/primitives
</div>
<CollapsibleContent class="space-y-2">
<div class="rounded-md border px-4 py-3 font-mono text-sm">
@radix-ui/colors
</div>
<div class="rounded-md border px-4 py-3 font-mono text-sm">
@stitches/react
</div>
</CollapsibleContent>
</Collapsible>
</InternalCard>
<InternalCard title="Combobox"></InternalCard>
<InternalCard title="Command"></InternalCard>
<InternalCard title="Context Menu"></InternalCard>
<InternalCard title="Data Table"></InternalCard>
<InternalCard title="Date Picker"></InternalCard>
<InternalCard title="Dialog">
<Button label="Open" @click="dialogIsOpen = true">Show Dialog</Button>
<Dialog v-model="dialogIsOpen">
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account
and remove your data from our servers.
</DialogDescription>
</DialogHeader>
<DialogFooter><Button variant="outline" @click="dialogIsOpen = false">Cancel</Button><Button>CTA
Button</Button>
</DialogFooter>
</Dialog>
</InternalCard>
<InternalCard title="Dropdown Menu"></InternalCard>
<InternalCard title="Hover Card"></InternalCard>
<InternalCard title="Input">
<Input type="email" placeholder="Email" />
</InternalCard>
<InternalCard title="Label">
<div class="flex items-center space-x-2">
<Checkbox id="terms2" v-model="checkboxEnabled2" />
<Label htmlFor="terms2">Accept terms and conditions</Label>
</div>
</InternalCard>
<InternalCard title="Menubar"></InternalCard>
<InternalCard title="Navigation Menu"></InternalCard>
<InternalCard title="Popover">
<Popover>
<PopoverTrigger><Button variant="outline">Open popover</Button></PopoverTrigger>
<PopoverContent class="w-80">
<div class="grid gap-4">
<div class="space-y-2">
<h4 class="font-medium leading-none">Dimensions</h4>
<p class="text-sm text-muted-foreground">
Set the dimensions for the layer.
</p>
</div>
<div class="grid gap-2">
<div class="grid grid-cols-3 items-center gap-4">
<Label htmlFor="width">Width</Label>
<Input id="width" defaultValue="100%" class="col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxWidth">Max. width</Label>
<Input id="maxWidth" defaultValue="300px" class="col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<Label htmlFor="height">Height</Label>
<Input id="height" defaultValue="25px" class="col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<Label htmlFor="maxHeight">Max. height</Label>
<Input id="maxHeight" defaultValue="none" class="col-span-2 h-8" />
</div>
</div>
</div>
</PopoverContent>
</Popover>
</InternalCard>
<InternalCard title="Progress"></InternalCard>
<InternalCard title="Radio Group">
<RadioGroup v-model="radioValue">
<RadioGroupItem v-for="option in radioOptions" :value="option" :id="option">
<Label :for="option">{{ option }}</Label>
</RadioGroupItem>
</RadioGroup>
</InternalCard>
<InternalCard title="Scroll Area"></InternalCard>
<InternalCard title="Select">
<Select v-model="selectedPerson.id">
<SelectTrigger class="w-[210px]">
{{ selectedPerson.name }}
</SelectTrigger>
<SelectContent>
<SelectLabel>Fruits</SelectLabel>
<SelectItem v-for="person in people" :key="person.id" :value="person.id"
:disabled="person.unavailable">{{ person.name }}</SelectItem>
</SelectContent>
</Select>
</InternalCard>
<InternalCard title="Separator">
<div>
<div className="space-y-1">
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
<p className="text-sm text-muted-foreground">
An open-source UI component library.
</p>
</div>
<Separator class="my-4" />
<div className="flex h-5 items-center space-x-4 text-sm">
<div>Blog</div>
<Separator orientation="vertical" />
<div>Docs</div>
<Separator orientation="vertical" />
<div>Source</div>
</div>
</div>
</InternalCard>
<InternalCard title="Sheet"></InternalCard>
<InternalCard title="Skeleton"></InternalCard>
<InternalCard title="Slider"></InternalCard>
<InternalCard title="Switch">
<div class="flex items-center space-x-2">
<Switch id="airplane-mode" v-model="enabled" />
<Label htmlFor="airplane-mode">Airplane Mode</Label>
</div>
</InternalCard>
<InternalCard title="Table"></InternalCard>
<InternalCard title="Tabs"></InternalCard>
<InternalCard title="Textarea"><Textarea placeholder="Type your message here." /></InternalCard>
<InternalCard title="Toast"></InternalCard>
<InternalCard title="Toggle">
<Toggle aria-label="Toggle italic" v-model="toggleEnabled">
<Icon icon="lucide:bold" class="h-4 w-4" />
</Toggle>
</InternalCard>
<InternalCard title="Tooltip">
<Tooltip>
<TooltipTrigger>Hover</TooltipTrigger>
</Tooltip>
</InternalCard>
</div>
</div>
</div>
</template>
<style>
body {
@apply bg-neutral-100;
}
</style>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,8 @@
<template>
<div>
<slot />
</div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,11 @@
<template>
<DisclosurePanel class="overflow-hidden text-sm">
<div class="pb-4 pt-0"><slot /></div>
</DisclosurePanel>
</template>
<script setup>
import {
DisclosurePanel,
} from '@headlessui/vue'
</script>

View File

@ -0,0 +1,11 @@
<template>
<div class="border-b w-full">
<Disclosure>
<slot />
</Disclosure>
</div>
</template>
<script setup>
import { Disclosure } from "@headlessui/vue"
</script>

View File

@ -0,0 +1,23 @@
<template>
<div class="flex">
<DisclosureButton class="flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-headlessui-state=open]>svg]:rotate-180">
<slot />
<Icon icon="heroicons:chevron-down" class="text-[16px] transition-transform duration-200" />
</DisclosureButton>
</div>
</template>
<script setup lang="ts">
import { DisclosureButton } from "@headlessui/vue";
import { Icon } from "@iconify/vue";
/*
import MorphConfig from "@/morph.config.js";
import { computed } from "vue";
const triggerClass = computed(() => {
if (MorphConfig.ui.accordion.trigger) return MorphConfig.ui.accordion.trigger;
return "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-headlessui-state=open]>svg]:rotate-180";
});
*/
</script>

View File

@ -0,0 +1,4 @@
export { default as Accordion } from "./Accordion.vue";
export { default as AccordionContent } from "./AccordionContent.vue";
export { default as AccordionItem } from "./AccordionItem.vue";
export { default as AccordionTrigger } from "./AccordionTrigger.vue";

View File

@ -0,0 +1,11 @@
<template>
<div class="relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11">
<slot />
</div>
</template>
<script setup>
const props = defineProps({
title: String
})
</script>

View File

@ -0,0 +1,3 @@
<template>
<div class="text-sm [&_p]:leading-relaxed"><slot/></div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<h5 class="mb-1 font-medium leading-none tracking-tight"><slot/></h5>
</template>

View File

@ -0,0 +1,3 @@
export { default as Alert } from "./Alert.vue";
export { default as AlertTitle } from "./AlertTitle.vue";
export { default as AlertDescription } from "./AlertDescription.vue";

View File

@ -0,0 +1,64 @@
<template>
<TransitionRoot :appear="appear" :show="isOpen" as="template">
<Dialog class="relative z-50" @close="close">
<TransitionChild v-if="overlay" as="template" :appear="appear" enter='ease-out duration-300'
enterFrom='opacity-0' enterTo='opacity-100' leave='ease-in duration-200' leaveFrom='opacity-100'
leaveTo='opacity-0'>
<div class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in" />
</TransitionChild>
<div class="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
<TransitionChild as="template" :appear="appear" enter='ease-out duration-300'
enterFrom='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
enterTo='opacity-100 translate-y-0 sm:scale-100' leave='ease-in duration-200'
leaveFrom='opacity-100 translate-y-0 sm:scale-100'
leaveTo='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'>
<DialogPanel
class="fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-white p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full">
<slot />
</DialogPanel>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
appear: {
type: Boolean,
default: false
},
overlay: {
type: Boolean,
default: true
},
transition: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'close'])
const isOpen = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
function close(value: boolean) {
isOpen.value = value
emit('close')
}
</script>

View File

@ -0,0 +1,3 @@
<template>
<p class="text-sm text-black/60"><slot/></p>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"><slot/></div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="flex flex-col space-y-2 text-center sm:text-left"><slot/></div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<h5 class="text-lg font-semibold">
<slot />
</h5>
</template>

View File

@ -0,0 +1,12 @@
<template>
<div class="flex items-center justify-center border-2 border-neutral-300 rounded-xl min-h-[300px] relative">
<p class="text-2xl font-semibold absolute left-5 top-3">{{ props.title }}</p>
<slot />
</div>
</template>
<script setup>
const props = defineProps({
title: String
})
</script>

View File

@ -0,0 +1,5 @@
export { default as AlertDialog } from "./AlertDialog.vue";
export { default as AlertDialogTitle } from "./AlertDialogTitle.vue";
export { default as AlertDialogHeader } from "./AlertDialogHeader.vue";
export { default as AlertDialogFooter } from "./AlertDialogFooter.vue";
export { default as AlertDialogDescription } from "./AlertDialogDescription.vue";

View File

@ -0,0 +1,18 @@
<template>
<div :class="`relative w-full`" :style="`padding-bottom: ${aspect}%`">
<div class="absolute inset-0">
<slot />
</div>
</div>
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
ratio: Number,
});
const aspect = computed(() => {
return 1/props.ratio*100;
});
</script>

View File

@ -0,0 +1,3 @@
<template>
<img style="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent;" />
</template>

View File

@ -0,0 +1,2 @@
export { default as AspectRatio } from "./AspectRatio.vue";
export { default as Image } from "./Image.vue";

View File

@ -0,0 +1,4 @@
<template>
<div class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full">
<slot />
</div></template>

View File

@ -0,0 +1 @@
<template><div class="flex h-full w-full items-center justify-center rounded-full bg-muted"><slot /></div></template>

View File

@ -0,0 +1,10 @@
<template>
<img :src="props.src" :alt="props.alt">
</template>
<script setup>
const props = defineProps({
src: String,
alt: String
})
</script>

View File

@ -0,0 +1,3 @@
export { default as Avatar } from "./Avatar.vue";
export { default as AvatarFallback } from "./AvatarFallback.vue";
export { default as AvatarImage } from "./AvatarImage.vue";

View File

@ -0,0 +1,8 @@
<template>
<div class="inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 bg-black hover:bg-black/80 border-transparent text-white">
<slot />
</div>
</template>
<script setup>
</script>

View File

@ -0,0 +1 @@
export { default as Badge } from "./Badge.vue";

View File

@ -0,0 +1,14 @@
<template>
<button
:class="`inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background h-10 py-2 px-4
${props.variant==='outline' ? 'border border-neutral-200 hover:bg-neutral-100' : 'bg-black text-white hover:bg-black/80' }`">
<slot />
</button>
</template>
<script setup>
const props = defineProps({
label: String,
variant: String,
});
</script>

View File

@ -0,0 +1 @@
export { default as Button } from "./Button.vue";

View File

@ -0,0 +1,5 @@
<template>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm">
<slot />
</div>
</template>

View File

@ -0,0 +1,7 @@
<template>
<div class="p-6 pt-0"><slot/></div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,7 @@
<template>
<p class="text-sm text-muted-foreground"><slot/></p>
</template>
<script setup>
</script>

View File

@ -0,0 +1,7 @@
<template>
<div class="flex items-center p-6 pt-0"><slot/></div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,7 @@
<template>
<div class="flex flex-col space-y-1.5 p-6"><slot/></div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,7 @@
<template>
<h3 class="text-lg font-semibold leading-none tracking-tight"><slot/></h3>
</template>
<script setup>
</script>

View File

@ -0,0 +1,6 @@
export { default as Card } from "./Card.vue";
export { default as CardContent } from "./CardContent.vue";
export { default as CardDescription } from "./CardDescription.vue";
export { default as CardFooter } from "./CardFooter.vue";
export { default as CardHeader } from "./CardHeader.vue";
export { default as CardTitle } from "./CardTitle.vue";

View File

@ -0,0 +1,28 @@
<template>
<Switch v-model="isOpen" :class="isOpen ? 'text-primary-foreground bg-primary' : ''"
class="peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground">
<span class="flex items-center justify-center text-current" v-if="isOpen"><Icon icon="lucide:check"/></span>
</Switch>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Switch } from '@headlessui/vue'
import { Icon } from "@iconify/vue";
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue', 'close'])
const isOpen = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>

View File

@ -0,0 +1 @@
export { default as Checkbox } from "./Checkbox.vue";

View File

@ -0,0 +1,9 @@
<template>
<Disclosure as="div" v-slot="{ open }">
<slot :open="open"></slot>
</Disclosure>
</template>
<script setup>
import { Disclosure } from "@headlessui/vue"
</script>

View File

@ -0,0 +1,11 @@
<template>
<DisclosurePanel class="overflow-hidden text-sm">
<slot />
</DisclosurePanel>
</template>
<script setup>
import {
DisclosurePanel,
} from '@headlessui/vue'
</script>

View File

@ -0,0 +1,9 @@
<template>
<DisclosureButton as="template">
<slot />
</DisclosureButton>
</template>
<script setup lang="ts">
import { DisclosureButton } from "@headlessui/vue";
</script>

View File

@ -0,0 +1,3 @@
export { default as Collapsible } from "./Collapsible.vue";
export { default as CollapsibleContent } from "./CollapsibleContent.vue";
export { default as CollapsibleTrigger } from "./CollapsibleTrigger.vue";

View File

@ -0,0 +1,44 @@
<template>
<Listbox v-model="isOpen" v-slot="{ open }">
<Float
portal
placement="bottom"
flip
strategy="fixed"
adaptive-width
enter="transition duration-200 ease-out"
enter-from="scale-95 opacity-0"
enter-to="scale-100 opacity-100"
leave="transition duration-150 ease-in"
leave-from="scale-100 opacity-100"
leave-to="scale-95 opacity-0"
tailwindcss-origin-class
:offset="4"
as="div"
class="relative mt-1"
>
<slot :open="open"></slot>
</Float>
</Listbox>
</template>
<script setup>
import { ref, computed } from "vue";
import { Listbox } from "@headlessui/vue";
import { Float } from "@headlessui-float/vue";
const props = defineProps({
modelValue: Boolean,
});
const emit = defineEmits(["update:modelValue", "close"]);
const isOpen = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
</script>

View File

@ -0,0 +1,16 @@
<template>
<ListboxButton v-slot="{ open }"
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
<span>
<slot />
</span>
<Icon icon="lucide:chevron-down" :class="`h-4 w-4 opacity-50 transition-all ${open ? 'rotate-180' : ''}`" />
</ListboxButton>
</template>
<script setup>
import {
ListboxButton,
} from '@headlessui/vue'
import { Icon } from "@iconify/vue";
</script>

View File

@ -0,0 +1,24 @@
<template>
<ListboxOption v-slot="{ active, selected, disabled }" as="template">
<li :class="[
active ? 'bg-accent text-accent-foreground' : 'text-gray-900',
disabled ? 'opacity-50 pointer-events-none' : 'text-gray-900',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
]">
<span v-if="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Icon icon="lucide:check" class="h-[14px] w-[14px]" aria-hidden="true" />
</span>
<span>
<slot />
</span>
</li>
</ListboxOption>
</template>
<script setup>
import {
ListboxOption,
} from '@headlessui/vue'
import { Icon } from "@iconify/vue";
</script>

View File

@ -0,0 +1,12 @@
<template>
<ListboxOptions
class="w-full min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md p-1 focus:outline-none">
<slot />
</ListboxOptions>
</template>
<script setup>
import {
ListboxOptions,
} from '@headlessui/vue'
</script>

View File

@ -0,0 +1,4 @@
export { default as Combobox } from "./Combobox.vue";
export { default as ComboboxInput } from "./ComboboxInput.vue";
export { default as ComboboxOptions } from "./ComboboxOptions.vue";
export { default as ComboboxOption } from "./ComboboxOption.vue";

View File

@ -0,0 +1,68 @@
<script setup>
import { Icon } from "@iconify/vue";
import { Button } from "@/src/components/Button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/src/components/Card"
const notifications = [
{
title: "Your call has been confirmed.",
description: "1 hour ago",
},
{
title: "You have a new message!",
description: "1 hour ago",
},
{
title: "Your subscription is expiring soon!",
description: "2 hours ago",
},
]
</script>
<template>
<Card class="w-[380px]">
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>You have 3 unread messages.</CardDescription>
</CardHeader>
<CardContent class="grid gap-4">
<div class=" flex items-center space-x-4 rounded-md border p-4">
<Icon icon="lucide:bell-ring" />
<div class="flex-1 space-y-1">
<p class="text-sm font-medium leading-none">
Push Notifications
</p>
<p class="text-sm text-muted-foreground">
Send notifications to device.
</p>
</div>
</div>
<div>
<div class="mb-4 grid grid-cols-[25px_1fr] items-start pb-4 last:mb-0 last:pb-0"
v-for="notification, index in notifications" :key="index">
<span class="flex h-2 w-2 translate-y-1 rounded-full bg-sky-500" />
<div class="space-y-1">
<p class="text-sm font-medium leading-none">
{{ notification.title }}
</p>
<p class="text-sm text-muted-foreground">
{{ notification.description }}
</p>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button class="w-full">
<Icon icon="lucide:check" class="mr-2 h-4 w-4" /> Mark all as read
</Button>
</CardFooter>
</Card>
</template>

View File

@ -0,0 +1 @@
export { default as Card } from "./Card.vue";

View File

@ -0,0 +1,64 @@
<template>
<TransitionRoot :appear="appear" :show="isOpen" as="template">
<Dialog class="relative z-50" @close="close">
<TransitionChild v-if="overlay" as="template" :appear="appear" enter='ease-out duration-300'
enterFrom='opacity-0' enterTo='opacity-100' leave='ease-in duration-200' leaveFrom='opacity-100'
leaveTo='opacity-0'>
<div class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in" />
</TransitionChild>
<div class="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
<TransitionChild as="template" :appear="appear" enter='ease-out duration-300'
enterFrom='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
enterTo='opacity-100 translate-y-0 sm:scale-100' leave='ease-in duration-200'
leaveFrom='opacity-100 translate-y-0 sm:scale-100'
leaveTo='opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'>
<DialogPanel
class="fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-white p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full">
<slot />
</DialogPanel>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
appear: {
type: Boolean,
default: false
},
overlay: {
type: Boolean,
default: true
},
transition: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'close'])
const isOpen = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
function close(value: boolean) {
isOpen.value = value
emit('close')
}
</script>

View File

@ -0,0 +1,3 @@
<template>
<p class="text-sm text-black/60"><slot/></p>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"><slot/></div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="flex flex-col space-y-2 text-center sm:text-left"><slot/></div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<h5 class="text-lg font-semibold">
<slot />
</h5>
</template>

View File

@ -0,0 +1,12 @@
<template>
<div class="flex items-center justify-center border-2 border-neutral-300 rounded-xl min-h-[300px] relative">
<p class="text-2xl font-semibold absolute left-5 top-3">{{ props.title }}</p>
<slot />
</div>
</template>
<script setup>
const props = defineProps({
title: String
})
</script>

View File

@ -0,0 +1,5 @@
export { default as Dialog } from "./Dialog.vue";
export { default as DialogTitle } from "./DialogTitle.vue";
export { default as DialogHeader } from "./DialogHeader.vue";
export { default as DialogFooter } from "./DialogFooter.vue";
export { default as DialogDescription } from "./DialogDescription.vue";

View File

@ -0,0 +1,11 @@
<template>
<input
:class="props.class"
class="flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
</template>
<script setup>
const props = defineProps({
class: String,
})
</script>

View File

@ -0,0 +1 @@
export { default as Input } from "./Input.vue";

View File

@ -0,0 +1,14 @@
<template>
<div class="flex items-center justify-center bg-white border border-neutral-200 shadow-sm rounded-xl h-[280px] overflow-hidden relative px-6">
<p class="text-xl font-semibold absolute left-4 top-3 rounded-lg bg-neutral-100 px-2 py-1 text-neutral-600">{{ props.title }}</p>
<div class="overflow-y-scroll flex-grow h-full max-h-full flex items-center justify-center pb-4 pt-12">
<slot />
</div>
</div>
</template>
<script setup>
const props = defineProps({
title: String
})
</script>

View File

@ -0,0 +1 @@
export { default as Card } from "./Card.vue";

View File

@ -0,0 +1,11 @@
<template>
<label :for="props.htmlFor" :class="`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${props.class}`"><slot/></label>
</template>
<script setup>
const props = defineProps({
class: String,
id: String,
htmlFor: String
})
</script>

View File

@ -0,0 +1 @@
export { default as Label } from "./Label.vue";

View File

@ -0,0 +1,15 @@
<template>
<Popover v-slot="{ open }">
<Float portal composable placement="bottom" flip strategy="fixed" enter="transition duration-200 ease-out"
enter-from="scale-95 opacity-0" enter-to="scale-100 opacity-100" leave="transition duration-150 ease-in"
leave-from="scale-100 opacity-100" leave-to="scale-95 opacity-0" tailwindcss-origin-class :offset="6" as="div"
class="relative">
<slot :open="open"/>
</Float>
</Popover>
</template>
<script setup>
import { Popover } from "@headlessui/vue";
import { Float } from "@headlessui-float/vue";
</script>

View File

@ -0,0 +1,13 @@
<template>
<FloatContent>
<PopoverPanel
class="z-50 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 w-80">
<slot />
</PopoverPanel>
</FloatContent>
</template>
<script setup>
import { PopoverPanel } from "@headlessui/vue";
import { FloatContent } from "@headlessui-float/vue";
</script>

View File

@ -0,0 +1,10 @@
<template>
<FloatReference>
<PopoverButton><slot /></PopoverButton>
</FloatReference>
</template>
<script setup>
import { PopoverButton } from "@headlessui/vue";
import { FloatReference } from "@headlessui-float/vue";
</script>

View File

@ -0,0 +1,3 @@
export { default as Popover } from "./Popover.vue";
export { default as PopoverContent } from "./PopoverContent.vue";
export { default as PopoverTrigger } from "./PopoverTrigger.vue";

View File

@ -0,0 +1,26 @@
<template>
<RadioGroup v-model="isOpen" class="flex flex-col gap-2">
<slot />
</RadioGroup>
</template>
<script setup>
import { ref, computed } from 'vue'
import { RadioGroup } from '@headlessui/vue';
const props = defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue', 'close'])
const isOpen = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>

View File

@ -0,0 +1,13 @@
<template>
<RadioGroupOption v-slot="{ active, checked }" class="list-none focus:outline-none flex items-center space-x-2 [&>[role='indicator']]:focus:outline-none [&>[role='indicator']]:focus-visible:ring-2 [&>[role='indicator']]:focus-visible:ring-ring [&>[role='indicator']]:focus-visible:ring-offset-2">
<button role="indicator"
class="aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 flex items-center justify-center">
<span v-show="checked" class="bg-black h-2.5 w-2.5 rounded-full"></span>
</button>
<slot />
</RadioGroupOption>
</template>
<script setup>
import { RadioGroupOption } from '@headlessui/vue';
</script>

View File

@ -0,0 +1,2 @@
export { default as RadioGroup } from "./RadioGroup.vue";
export { default as RadioGroupItem } from "./RadioGroupItem.vue";

View File

@ -0,0 +1,44 @@
<template>
<Listbox v-model="isOpen" v-slot="{ open }">
<Float
portal
placement="bottom"
flip
strategy="fixed"
adaptive-width
enter="transition duration-200 ease-out"
enter-from="scale-95 opacity-0"
enter-to="scale-100 opacity-100"
leave="transition duration-150 ease-in"
leave-from="scale-100 opacity-100"
leave-to="scale-95 opacity-0"
tailwindcss-origin-class
:offset="4"
as="div"
class="relative mt-1"
>
<slot :open="open"></slot>
</Float>
</Listbox>
</template>
<script setup>
import { ref, computed } from "vue";
import { Listbox } from "@headlessui/vue";
import { Float } from "@headlessui-float/vue";
const props = defineProps({
modelValue: Number,
});
const emit = defineEmits(["update:modelValue", "close"]);
const isOpen = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
</script>

View File

@ -0,0 +1,12 @@
<template>
<ListboxOptions
class="w-full min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md p-1 focus:outline-none">
<slot />
</ListboxOptions>
</template>
<script setup>
import {
ListboxOptions,
} from '@headlessui/vue'
</script>

View File

@ -0,0 +1,24 @@
<template>
<ListboxOption v-slot="{ active, selected, disabled }" as="template">
<li :class="[
active ? 'bg-accent text-accent-foreground' : 'text-gray-900',
disabled ? 'opacity-50 pointer-events-none' : 'text-gray-900',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none',
]">
<span v-if="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<Icon icon="lucide:check" class="h-[14px] w-[14px]" aria-hidden="true" />
</span>
<span>
<slot />
</span>
</li>
</ListboxOption>
</template>
<script setup>
import {
ListboxOption,
} from '@headlessui/vue'
import { Icon } from "@iconify/vue";
</script>

View File

@ -0,0 +1,15 @@
<template>
<div class="py-1.5 pl-8 pr-2 text-sm font-semibold">
<ListboxLabel>
<slot />
</ListboxLabel>
</div>
</template>
<script setup>
import {
ListboxLabel,
} from '@headlessui/vue'
</script>

View File

@ -0,0 +1,16 @@
<template>
<ListboxButton v-slot="{ open }"
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
<span>
<slot />
</span>
<Icon icon="lucide:chevron-down" :class="`h-4 w-4 opacity-50 transition-all ${open ? 'rotate-180' : ''}`" />
</ListboxButton>
</template>
<script setup>
import {
ListboxButton,
} from '@headlessui/vue'
import { Icon } from "@iconify/vue";
</script>

View File

@ -0,0 +1,5 @@
export { default as Select } from "./Select.vue";
export { default as SelectContent } from "./SelectContent.vue";
export { default as SelectTrigger } from "./SelectTrigger.vue";
export { default as SelectItem } from "./SelectItem.vue";
export { default as SelectLabel } from "./SelectLabel.vue";

View File

@ -0,0 +1,13 @@
<template>
<div :class="`shrink-0 bg-border ${props.orientation == 'vertical' ? 'w-[1px] h-full' : 'w-full h-[1px]'}`"></div>
</template>
<script setup>
const props = defineProps({
orientation: {
type: String,
required: false,
default: "horizontal"
}
})
</script>

View File

@ -0,0 +1 @@
export {default as Separator} from './Separator.vue'

View File

@ -0,0 +1,28 @@
<template>
<Switch v-model="isOpen" :class="isOpen ? 'bg-black' : 'bg-neutral-300'"
class="peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50">
<span :class="isOpen ? 'translate-x-5' : 'translate-x-0'"
class="pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform" />
</Switch>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Switch } from '@headlessui/vue'
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue', 'close'])
const isOpen = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>

View File

@ -0,0 +1 @@
export { default as Switch } from "./Switch.vue";

View File

@ -0,0 +1,9 @@
<template>
<TabGroup>
<slot />
</TabGroup>
</template>
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
</script>

View File

@ -0,0 +1,7 @@
<template>
<div><slot/></div>
</template>
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
</script>

View File

@ -0,0 +1,7 @@
<template>
<div><slot/></div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,7 @@
<template>
<div><slot/></div>
</template>
<script setup>
</script>

View File

@ -0,0 +1,4 @@
export { default as Tabs } from "./Tabs.vue";
export { default as TabsContent } from "./TabsContent.vue";
export { default as TabsList } from "./TabsList.vue";
export { default as TabsTrigger } from "./TabsTrigger.vue";

View File

@ -0,0 +1,7 @@
<template>
<textarea
class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
</template>
<script setup>
</script>

View File

@ -0,0 +1 @@
export { default as Textarea } from "./Textarea.vue";

View File

@ -0,0 +1,29 @@
<template>
<Switch v-model="isChecked" :class="isChecked ? 'text-accent-foreground bg-neutral-300' : 'bg-neutral-100'"
class="inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background hover:bg-neutral-200/80 hover:text-black-40 h-10 px-3">
<span>
<slot />
</span>
</Switch>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Switch } from '@headlessui/vue'
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue', 'close'])
const isChecked = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>

View File

@ -0,0 +1 @@
export { default as Toggle } from "./Toggle.vue";

View File

@ -0,0 +1,21 @@
<template>
<Float :show="show">
<slot :enter="handleMouseEnter" :leave="handleMouseLeave"/>
<div static>
Help me fund my Open Source work!
</div>
</Float>
</template>
<script setup>
import { ref } from 'vue'
import { Float } from '@headlessui-float/vue'
const show = ref(false)
const handleMouseEnter = () => {
show.value = true
}
const handleMouseLeave = () => {
show.value = false
}
</script>

Some files were not shown because too many files have changed in this diff Show More