@hemia/lume-registry 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +43 -0
  2. package/package.json +14 -0
  3. package/registry/react/.gitkeep +0 -0
  4. package/registry/vue/alert/alert-action.vue +14 -0
  5. package/registry/vue/alert/alert-description.vue +14 -0
  6. package/registry/vue/alert/alert-title.vue +14 -0
  7. package/registry/vue/alert/alert.vue +42 -0
  8. package/registry/vue/alert/index.ts +4 -0
  9. package/registry/vue/alert/meta.json +19 -0
  10. package/registry/vue/alertdialog/alertdialog-action.vue +20 -0
  11. package/registry/vue/alertdialog/alertdialog-cancel.vue +32 -0
  12. package/registry/vue/alertdialog/alertdialog-content.vue +75 -0
  13. package/registry/vue/alertdialog/alertdialog-description.vue +13 -0
  14. package/registry/vue/alertdialog/alertdialog-footer.vue +34 -0
  15. package/registry/vue/alertdialog/alertdialog-header.vue +16 -0
  16. package/registry/vue/alertdialog/alertdialog-media.vue +13 -0
  17. package/registry/vue/alertdialog/alertdialog-title.vue +13 -0
  18. package/registry/vue/alertdialog/alertdialog-trigger.vue +29 -0
  19. package/registry/vue/alertdialog/alertdialog.vue +25 -0
  20. package/registry/vue/alertdialog/index.ts +10 -0
  21. package/registry/vue/alertdialog/meta.json +20 -0
  22. package/registry/vue/badge/badge.vue +39 -0
  23. package/registry/vue/badge/index.ts +1 -0
  24. package/registry/vue/badge/meta.json +16 -0
  25. package/registry/vue/button/button.vue +52 -0
  26. package/registry/vue/button/index.ts +1 -0
  27. package/registry/vue/button/meta.json +16 -0
  28. package/registry/vue/card/card-action.vue +21 -0
  29. package/registry/vue/card/card-content.vue +20 -0
  30. package/registry/vue/card/card-description.vue +13 -0
  31. package/registry/vue/card/card-footer.vue +24 -0
  32. package/registry/vue/card/card-header.vue +21 -0
  33. package/registry/vue/card/card-title.vue +13 -0
  34. package/registry/vue/card/card.vue +21 -0
  35. package/registry/vue/card/index.ts +7 -0
  36. package/registry/vue/card/meta.json +18 -0
  37. package/registry/vue/field/field-description.vue +16 -0
  38. package/registry/vue/field/field-group.vue +22 -0
  39. package/registry/vue/field/field-label.vue +19 -0
  40. package/registry/vue/field/field-legend.vue +16 -0
  41. package/registry/vue/field/field-separator.vue +14 -0
  42. package/registry/vue/field/field.vue +29 -0
  43. package/registry/vue/field/fieldset.vue +13 -0
  44. package/registry/vue/field/index.ts +7 -0
  45. package/registry/vue/field/meta.json +22 -0
  46. package/registry/vue/icon/icon.vue +37 -0
  47. package/registry/vue/icon/index.ts +2 -0
  48. package/registry/vue/icon/meta.json +16 -0
  49. package/registry/vue/textfield/index.ts +1 -0
  50. package/registry/vue/textfield/meta.json +16 -0
  51. package/registry/vue/textfield/textfield.vue +157 -0
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @hemia/lume-registry
2
+
3
+ Component templates registry for the Lume UI system. Contains source code for all Lume components organized by framework.
4
+
5
+ ## What is this?
6
+
7
+ This package stores the component templates that the Lume CLI copies into user projects. **Users don't install this directly** — it's consumed by the `@hemia/lume` CLI.
8
+
9
+ ## Structure
10
+
11
+ ```
12
+ registry/
13
+ ├── vue/
14
+ │ ├── button/
15
+ │ │ ├── button.vue
16
+ │ │ └── meta.json
17
+ │ └── card/
18
+ │ ├── card.vue
19
+ │ └── meta.json
20
+ └── react/ (coming soon)
21
+ ```
22
+
23
+ ## How it works
24
+
25
+ When a user runs:
26
+
27
+ ```bash
28
+ bunx @hemia/lume@latest add button
29
+ ```
30
+
31
+ The CLI:
32
+ 1. Reads the framework from `lume.config.json`
33
+ 2. Resolves `@hemia/lume-registry` package location
34
+ 3. Copies files from `registry/{framework}/{component}/` to the user's project
35
+ 4. Installs dependencies listed in `meta.json`
36
+
37
+ ## Usage
38
+
39
+ This package is dependency of `@hemia/lume` CLI. It's not meant to be imported in user code.
40
+
41
+ ## License
42
+
43
+ MIT
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@hemia/lume-registry",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "files": ["registry"],
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "devDependencies": {
10
+ "@hemia/lume-vue": "workspace:*",
11
+ "class-variance-authority": "^0.7.1",
12
+ "vue": "^3.4.0"
13
+ }
14
+ }
File without changes
@@ -0,0 +1,14 @@
1
+ <!-- eslint-disable vue/no-parsing-error -->
2
+ <script setup lang="ts">
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const props = defineProps<{
6
+ class?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div :class="cn('flex items-center gap-2', props.class)">
12
+ <slot />
13
+ </div>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <!-- eslint-disable vue/no-parsing-error -->
2
+ <script setup lang="ts">
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const props = defineProps<{
6
+ class?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <div :class="cn('text-sm [&_p]:leading-relaxed', props.class)">
12
+ <slot />
13
+ </div>
14
+ </template>
@@ -0,0 +1,14 @@
1
+ <!-- eslint-disable vue/no-parsing-error -->
2
+ <script setup lang="ts">
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const props = defineProps<{
6
+ class?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
12
+ <slot />
13
+ </h5>
14
+ </template>
@@ -0,0 +1,42 @@
1
+ <!-- eslint-disable vue/no-parsing-error -->
2
+ <script setup lang="ts">
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "@hemia/lume-vue"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-muted/50 text-foreground border-border",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive bg-destructive/10 dark:bg-destructive/20",
14
+ tonal:
15
+ "border-transparent bg-destructive/10 text-destructive dark:bg-destructive/30 dark:text-destructive-200",
16
+ success:
17
+ "border-transparent bg-emerald-500/10 text-emerald-700 dark:text-emerald-200 dark:bg-emerald-500/30",
18
+ warning:
19
+ "border-transparent bg-amber-500/10 text-amber-700 dark:text-amber-200 dark:bg-amber-500/30",
20
+ info:
21
+ "border-transparent bg-blue-500/10 text-blue-700 dark:text-blue-200 dark:bg-blue-500/30",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ }
28
+ )
29
+
30
+ type AlertVariants = VariantProps<typeof alertVariants>
31
+
32
+ const props = defineProps<{
33
+ variant?: AlertVariants["variant"]
34
+ class?: string
35
+ }>()
36
+ </script>
37
+
38
+ <template>
39
+ <div :class="cn(alertVariants({ variant }), props.class)" role="alert">
40
+ <slot />
41
+ </div>
42
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Alert } from "./alert.vue"
2
+ export { default as AlertTitle } from "./alert-title.vue"
3
+ export { default as AlertDescription } from "./alert-description.vue"
4
+ export { default as AlertAction } from "./alert-action.vue"
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "alert",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "alert.vue",
7
+ "alert-title.vue",
8
+ "alert-description.vue",
9
+ "alert-action.vue",
10
+ "index.ts"
11
+ ],
12
+ "registryDependencies": [],
13
+ "dependencies": [
14
+ "class-variance-authority"
15
+ ],
16
+ "peerDependencies": [
17
+ "@hemia/lume-vue"
18
+ ]
19
+ }
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+ import { Button } from '../button'
5
+
6
+ const size = inject<string>('alert-dialog-size', 'md')
7
+
8
+ const props = defineProps<{
9
+ variant?: 'default' | 'destructive'
10
+ class?: string
11
+ }>()
12
+
13
+ const isSm = computed(() => size === 'sm')
14
+ </script>
15
+
16
+ <template>
17
+ <Button :variant="props.variant" :class="cn(isSm && 'w-full justify-center', props.class)">
18
+ <slot />
19
+ </Button>
20
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import { inject, type Ref, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+ import { Button } from '../button'
5
+
6
+ const dialog = inject<{
7
+ open: Ref<boolean>
8
+ onOpenChange: (value: boolean) => void
9
+ }>('alert-dialog')
10
+
11
+ const size = inject<string>('alert-dialog-size', 'md')
12
+
13
+ const props = defineProps<{
14
+ class?: string
15
+ }>()
16
+
17
+ function close() {
18
+ dialog?.onOpenChange(false)
19
+ }
20
+
21
+ const isSm = computed(() => size === 'sm')
22
+ </script>
23
+
24
+ <template>
25
+ <Button
26
+ variant="outline"
27
+ :class="cn(isSm && 'w-full justify-center', props.class)"
28
+ @click="close"
29
+ >
30
+ <slot />
31
+ </Button>
32
+ </template>
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ import { inject, type Ref, computed, provide } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const dialog = inject<{
6
+ open: Ref<boolean>
7
+ onOpenChange: (value: boolean) => void
8
+ }>('alert-dialog')
9
+
10
+ const props = defineProps<{
11
+ class?: string
12
+ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
13
+ persistent?: boolean
14
+ }>()
15
+
16
+ // Provide size to child components
17
+ provide('alert-dialog-size', props.size || 'md')
18
+
19
+ function close() {
20
+ dialog?.onOpenChange(false)
21
+ }
22
+
23
+ const isOpen = computed(() => dialog?.open.value ?? false)
24
+
25
+ const sizeClasses = computed(() => {
26
+ const sizes = {
27
+ sm: 'max-w-sm',
28
+ md: 'max-w-lg',
29
+ lg: 'max-w-2xl',
30
+ xl: 'max-w-4xl',
31
+ full: 'max-w-[calc(100vw-2rem)] h-[calc(100vh-2rem)]'
32
+ }
33
+ return sizes[props.size || 'md']
34
+ })
35
+
36
+ const isCentered = computed(() => props.size === 'sm')
37
+ </script>
38
+
39
+ <template>
40
+ <Teleport to="body">
41
+ <Transition
42
+ enter-active-class="transition-all duration-200 ease-out"
43
+ enter-from-class="opacity-0"
44
+ enter-to-class="opacity-100"
45
+ leave-active-class="transition-all duration-150 ease-in"
46
+ leave-from-class="opacity-100"
47
+ leave-to-class="opacity-0"
48
+ >
49
+ <div
50
+ v-if="isOpen"
51
+ class="fixed inset-0 z-50 bg-black/80"
52
+ :class="{ 'cursor-not-allowed pointer-events-none': props.persistent }"
53
+ @click="!props.persistent && close()"
54
+ />
55
+ </Transition>
56
+ </Teleport>
57
+ <Teleport to="body">
58
+ <Transition
59
+ enter-active-class="transition-all duration-200 ease-out"
60
+ enter-from-class="opacity-0 scale-95 translate-y-4"
61
+ enter-to-class="opacity-100 scale-100 translate-y-0"
62
+ leave-active-class="transition-all duration-150 ease-in"
63
+ leave-from-class="opacity-100 scale-100 translate-y-0"
64
+ leave-to-class="opacity-0 scale-95 translate-y-4"
65
+ >
66
+ <div
67
+ v-if="isOpen"
68
+ class="fixed left-[50%] top-[50%] z-50 grid w-full translate-x-[-50%] translate-y-[-50%] gap-0 border bg-background shadow-lg duration-200 sm:rounded-lg"
69
+ :class="cn(sizeClasses, isCentered && 'text-center', props.class)"
70
+ >
71
+ <slot />
72
+ </div>
73
+ </Transition>
74
+ </Teleport>
75
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <p :class="cn('text-sm text-muted-foreground', props.class)">
11
+ <slot />
12
+ </p>
13
+ </template>
@@ -0,0 +1,34 @@
1
+ <script setup lang="ts">
2
+ import { inject, type Ref, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const dialog = inject<{
6
+ open: Ref<boolean>
7
+ }>('alert-dialog')
8
+
9
+ const props = defineProps<{
10
+ class?: string
11
+ }>()
12
+
13
+ const isOpen = computed(() => dialog?.open.value ?? false)
14
+
15
+ // Obtener el tamaño del content a través del provide/inject
16
+ const size = inject<string>('alert-dialog-size', 'md')
17
+
18
+ const isSm = computed(() => size === 'sm')
19
+ </script>
20
+
21
+ <template>
22
+ <div
23
+ :class="cn(
24
+ isSm
25
+ ? 'flex-col-reverse'
26
+ : 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
27
+ 'bg-muted/50 rounded-b-lg px-6 py-4 dark:bg-muted/30',
28
+ isSm && 'grid grid-cols-2 gap-2',
29
+ props.class
30
+ )"
31
+ >
32
+ <slot />
33
+ </div>
34
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <div :class="cn('flex flex-col space-y-1.5 text-center sm:text-left px-6 pt-6 pb-2', props.class)">
11
+ <div v-if="$slots.media" class="flex justify-center">
12
+ <slot name="media" />
13
+ </div>
14
+ <slot />
15
+ </div>
16
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <div :class="cn('w-auto inline-flex items-center justify-center rounded-md p-3', props.class)">
11
+ <slot />
12
+ </div>
13
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <h2 :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
11
+ <slot />
12
+ </h2>
13
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { inject, type Ref } from 'vue'
3
+
4
+ const dialog = inject<{
5
+ open: Ref<boolean>
6
+ onOpenChange: (value: boolean) => void
7
+ }>('alert-dialog')
8
+
9
+ const props = defineProps<{
10
+ asChild?: boolean
11
+ class?: string
12
+ }>()
13
+
14
+ function onClick(event: MouseEvent) {
15
+ // Stop propagation to prevent double-firing
16
+ event.stopPropagation()
17
+ dialog?.onOpenChange(true)
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <component
23
+ :is="props.asChild ? 'span' : 'button'"
24
+ :class="props.class"
25
+ @click="onClick"
26
+ >
27
+ <slot />
28
+ </component>
29
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+ import { ref, provide } from 'vue'
4
+
5
+ const open = ref(false)
6
+
7
+ function onOpenChange(value: boolean) {
8
+ open.value = value
9
+ }
10
+
11
+ provide('alert-dialog', {
12
+ open,
13
+ onOpenChange,
14
+ })
15
+
16
+ const props = defineProps<{
17
+ class?: string
18
+ }>()
19
+ </script>
20
+
21
+ <template>
22
+ <div :class="cn(props.class)">
23
+ <slot :open="open" :on-open-change="onOpenChange" />
24
+ </div>
25
+ </template>
@@ -0,0 +1,10 @@
1
+ export { default as AlertDialog } from "./alertdialog.vue"
2
+ export { default as AlertDialogTrigger } from "./alertdialog-trigger.vue"
3
+ export { default as AlertDialogContent } from "./alertdialog-content.vue"
4
+ export { default as AlertDialogHeader } from "./alertdialog-header.vue"
5
+ export { default as AlertDialogFooter } from "./alertdialog-footer.vue"
6
+ export { default as AlertDialogTitle } from "./alertdialog-title.vue"
7
+ export { default as AlertDialogDescription } from "./alertdialog-description.vue"
8
+ export { default as AlertDialogCancel } from "./alertdialog-cancel.vue"
9
+ export { default as AlertDialogAction } from "./alertdialog-action.vue"
10
+ export { default as AlertDialogMedia } from "./alertdialog-media.vue"
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "alert-dialog",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "alertdialog.vue",
7
+ "alertdialog-trigger.vue",
8
+ "alertdialog-content.vue",
9
+ "alertdialog-header.vue",
10
+ "alertdialog-footer.vue",
11
+ "alertdialog-title.vue",
12
+ "alertdialog-description.vue",
13
+ "alertdialog-cancel.vue",
14
+ "alertdialog-action.vue",
15
+ "index.ts"
16
+ ],
17
+ "registryDependencies": ["button"],
18
+ "dependencies": [],
19
+ "peerDependencies": ["@hemia/lume-vue"]
20
+ }
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center rounded-md border 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",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary:
13
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
16
+ outline: "text-foreground",
17
+ ghost: "border-transparent hover:bg-accent hover:text-accent-foreground",
18
+ link: "border-transparent text-primary underline-offset-4 hover:underline",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ },
24
+ }
25
+ )
26
+
27
+ type BadgeVariants = VariantProps<typeof badgeVariants>
28
+
29
+ defineProps<{
30
+ variant?: BadgeVariants["variant"]
31
+ class?: string
32
+ }>()
33
+ </script>
34
+
35
+ <template>
36
+ <div :class="cn(badgeVariants({ variant }), $props.class)">
37
+ <slot />
38
+ </div>
39
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Badge } from "./badge.vue"
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "badge",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "badge.vue",
7
+ "index.ts"
8
+ ],
9
+ "registryDependencies": [],
10
+ "dependencies": [
11
+ "class-variance-authority"
12
+ ],
13
+ "peerDependencies": [
14
+ "@hemia/lume-vue"
15
+ ]
16
+ }
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const buttonVariants = cva(
6
+ "group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-[calc(var(--radius)*2.6)] border border-transparent bg-clip-padding text-sm font-medium transition-all outline-none select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
11
+ destructive:
12
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
13
+ outline:
14
+ "border-input bg-background hover:bg-accent hover:text-accent-foreground",
15
+ secondary:
16
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
17
+ ghost: "hover:bg-accent hover:text-accent-foreground",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ },
20
+ size: {
21
+ default: "h-9 px-4 py-2",
22
+ xs: "h-7 rounded-md px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
23
+ sm: "h-8 rounded-md px-3",
24
+ lg: "h-10 rounded-md px-8",
25
+ icon: "size-9",
26
+ "icon-xs": "size-7 [&_svg:not([class*='size-'])]:size-3",
27
+ "icon-sm": "size-8",
28
+ "icon-lg": "size-10",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ }
36
+ )
37
+
38
+ type ButtonVariants = VariantProps<typeof buttonVariants>
39
+
40
+ const props = defineProps<{
41
+ variant?: ButtonVariants["variant"]
42
+ size?: ButtonVariants["size"]
43
+ type?: "button" | "submit" | "reset"
44
+ class?: string
45
+ }>()
46
+ </script>
47
+
48
+ <template>
49
+ <button :type="props.type" :class="cn(buttonVariants({ variant, size }), props.class)">
50
+ <slot />
51
+ </button>
52
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Button } from "./button.vue"
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "button",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "button.vue",
7
+ "index.ts"
8
+ ],
9
+ "registryDependencies": [],
10
+ "dependencies": [
11
+ "class-variance-authority"
12
+ ],
13
+ "peerDependencies": [
14
+ "@hemia/lume-vue"
15
+ ]
16
+ }
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const size = inject<string>('card-size', 'default')
6
+ const isSm = computed(() => size === 'sm')
7
+
8
+ const props = defineProps<{
9
+ class?: string
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div :class="cn(
15
+ 'absolute right-4 top-4 flex items-center gap-2',
16
+ isSm ? 'right-2 top-2' : '',
17
+ props.class
18
+ )">
19
+ <slot />
20
+ </div>
21
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const size = inject<string>('card-size', 'default')
6
+ const isSm = computed(() => size === 'sm')
7
+
8
+ const props = defineProps<{
9
+ class?: string
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div :class="cn(
15
+ isSm ? 'pt-3 pb-3' : 'pt-6 pb-6',
16
+ props.class
17
+ )">
18
+ <slot />
19
+ </div>
20
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <p :class="cn('text-sm text-muted-foreground', props.class)">
11
+ <slot />
12
+ </p>
13
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const size = inject<string>('card-size', 'default')
6
+
7
+ const isSm = computed(() => size === 'sm')
8
+
9
+ const props = defineProps<{
10
+ class?: string
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <div :class="cn(
16
+ isSm
17
+ ? 'flex-col-reverse w-[calc(100%+1rem)] -mx-2 -mb-2 px-2'
18
+ : 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 -mx-6 -mb-6 px-6',
19
+ 'bg-muted/50 rounded-b-xl dark:bg-muted/30 py-3',
20
+ props.class
21
+ )">
22
+ <slot />
23
+ </div>
24
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const size = inject<string>('card-size', 'default')
6
+ const isSm = computed(() => size === 'sm')
7
+
8
+ const props = defineProps<{
9
+ class?: string
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div :class="cn(
15
+ 'flex flex-col',
16
+ isSm ? 'space-y-2 pr-6 mb-2' : 'space-y-1.5',
17
+ props.class
18
+ )">
19
+ <slot />
20
+ </div>
21
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from '@hemia/lume-vue'
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <h3 :class="cn('font-semibold leading-none tracking-tight', props.class)">
11
+ <slot />
12
+ </h3>
13
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { provide } from 'vue'
3
+ import { cn } from '@hemia/lume-vue'
4
+
5
+ const props = defineProps<{
6
+ size?: 'default' | 'sm'
7
+ class?: string
8
+ }>()
9
+
10
+ provide('card-size', props.size || 'default')
11
+ </script>
12
+
13
+ <template>
14
+ <div :class="cn(
15
+ 'relative rounded-xl border bg-card text-card-foreground shadow',
16
+ size === 'sm' ? 'p-2 pb-2' : 'p-6',
17
+ props.class
18
+ )">
19
+ <slot />
20
+ </div>
21
+ </template>
@@ -0,0 +1,7 @@
1
+ export { default as Card } from "./card.vue"
2
+ export { default as CardHeader } from "./card-header.vue"
3
+ export { default as CardTitle } from "./card-title.vue"
4
+ export { default as CardDescription } from "./card-description.vue"
5
+ export { default as CardContent } from "./card-content.vue"
6
+ export { default as CardFooter } from "./card-footer.vue"
7
+ export { default as CardAction } from "./card-action.vue"
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "card",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "card.vue",
7
+ "card-header.vue",
8
+ "card-title.vue",
9
+ "card-description.vue",
10
+ "card-content.vue",
11
+ "card-footer.vue",
12
+ "card-action.vue",
13
+ "index.ts"
14
+ ],
15
+ "registryDependencies": [],
16
+ "dependencies": [],
17
+ "peerDependencies": ["@hemia/lume-vue"]
18
+ }
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { cva } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldDescriptionVariants = cva("text-[0.8rem] text-muted-foreground")
6
+
7
+ const props = defineProps<{
8
+ class?: string
9
+ }>()
10
+ </script>
11
+
12
+ <template>
13
+ <p :class="cn(fieldDescriptionVariants(), props.class)">
14
+ <slot />
15
+ </p>
16
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import { cva } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldGroupVariants = cva(
6
+ "flex flex-wrap items-center gap-2 has-[input:focus]:rounded-lg has-[input:focus]:border-ring has-[input:focus]:ring-3 has-[input:focus]:ring-ring/50"
7
+ )
8
+
9
+ const props = defineProps<{
10
+ class?: string
11
+ }>()
12
+
13
+ defineOptions({
14
+ inheritAttrs: false
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <div :class="cn(fieldGroupVariants(), props.class)" v-bind="$attrs">
20
+ <slot />
21
+ </div>
22
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { cva } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldLabelVariants = cva(
6
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
7
+ )
8
+
9
+ const props = defineProps<{
10
+ forId?: string
11
+ class?: string
12
+ }>()
13
+ </script>
14
+
15
+ <template>
16
+ <label :for="forId" :class="cn(fieldLabelVariants(), props.class)">
17
+ <slot />
18
+ </label>
19
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { cva } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldLegendVariants = cva("text-sm font-medium leading-none")
6
+
7
+ const props = defineProps<{
8
+ class?: string
9
+ }>()
10
+ </script>
11
+
12
+ <template>
13
+ <legend :class="cn(fieldLegendVariants(), props.class)">
14
+ <slot />
15
+ </legend>
16
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ import { cva } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldSeparatorVariants = cva("h-px bg-border")
6
+
7
+ const props = defineProps<{
8
+ class?: string
9
+ }>()
10
+ </script>
11
+
12
+ <template>
13
+ <div :class="cn(fieldSeparatorVariants(), props.class)" />
14
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const fieldVariants = cva("grid gap-1.5 rounded-lg", {
6
+ variants: {
7
+ orientation: {
8
+ horizontal: "grid-cols-[1fr,auto]",
9
+ vertical: "",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ orientation: "vertical",
14
+ },
15
+ })
16
+
17
+ type FieldVariants = VariantProps<typeof fieldVariants>
18
+
19
+ const props = defineProps<{
20
+ orientation?: FieldVariants["orientation"]
21
+ class?: string
22
+ }>()
23
+ </script>
24
+
25
+ <template>
26
+ <div :class="cn(fieldVariants({ orientation }), props.class)">
27
+ <slot />
28
+ </div>
29
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { cn } from "@hemia/lume-vue"
3
+
4
+ const props = defineProps<{
5
+ class?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <fieldset :class="cn('space-y-2.5', props.class)">
11
+ <slot />
12
+ </fieldset>
13
+ </template>
@@ -0,0 +1,7 @@
1
+ export { default as Field } from "./field.vue"
2
+ export { default as FieldLabel } from "./field-label.vue"
3
+ export { default as FieldDescription } from "./field-description.vue"
4
+ export { default as FieldGroup } from "./field-group.vue"
5
+ export { default as FieldLegend } from "./field-legend.vue"
6
+ export { default as FieldSeparator } from "./field-separator.vue"
7
+ export { default as FieldSet } from "./fieldset.vue"
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "field",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "field.vue",
7
+ "field-label.vue",
8
+ "field-description.vue",
9
+ "field-group.vue",
10
+ "field-legend.vue",
11
+ "field-separator.vue",
12
+ "fieldset.vue",
13
+ "index.ts"
14
+ ],
15
+ "registryDependencies": [],
16
+ "dependencies": [
17
+ "class-variance-authority"
18
+ ],
19
+ "peerDependencies": [
20
+ "@hemia/lume-vue"
21
+ ]
22
+ }
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+
5
+ const iconVariants = cva(
6
+ "inline-flex shrink-0 items-center justify-center",
7
+ {
8
+ variants: {
9
+ size: {
10
+ default: "size-4",
11
+ sm: "size-3",
12
+ lg: "size-5",
13
+ xl: "size-6",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ size: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ type IconVariants = VariantProps<typeof iconVariants>
23
+
24
+ // Export for external use
25
+ defineExpose({ iconVariants, IconVariants })
26
+
27
+ defineProps<{
28
+ size?: IconVariants["size"]
29
+ class?: string
30
+ }>()
31
+ </script>
32
+
33
+ <template>
34
+ <span :class="cn(iconVariants({ size }), props.class)">
35
+ <slot />
36
+ </span>
37
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Icon } from "./icon.vue"
2
+ export { iconVariants, type IconVariants } from "./icon.variants"
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "icon",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "icon.vue",
7
+ "index.ts"
8
+ ],
9
+ "registryDependencies": [],
10
+ "dependencies": [
11
+ "class-variance-authority"
12
+ ],
13
+ "peerDependencies": [
14
+ "@hemia/lume-vue"
15
+ ]
16
+ }
@@ -0,0 +1 @@
1
+ export { default as TextField } from "./textfield.vue"
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "textfield",
3
+ "framework": "vue",
4
+ "type": "component",
5
+ "files": [
6
+ "textfield.vue",
7
+ "index.ts"
8
+ ],
9
+ "registryDependencies": ["field"],
10
+ "dependencies": [
11
+ "class-variance-authority"
12
+ ],
13
+ "peerDependencies": [
14
+ "@hemia/lume-vue"
15
+ ]
16
+ }
@@ -0,0 +1,157 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "@hemia/lume-vue"
4
+ import { Field, FieldLabel, FieldDescription, FieldGroup } from "../field/index"
5
+
6
+ const textfieldVariants = cva(
7
+ "flex h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "",
12
+ error: "bg-background border-destructive aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
13
+ },
14
+ size: {
15
+ default: "",
16
+ sm: "h-8",
17
+ lg: "h-10",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ size: "default",
23
+ },
24
+ }
25
+ )
26
+
27
+ const textfieldIconVariants = cva(
28
+ "text-muted-foreground flex shrink-0 items-center",
29
+ {
30
+ variants: {
31
+ size: {
32
+ default: "size-4",
33
+ sm: "size-3.5",
34
+ lg: "size-5",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ size: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ type TextfieldVariants = VariantProps<typeof textfieldVariants>
44
+ type TextfieldIconVariants = VariantProps<typeof textfieldIconVariants>
45
+
46
+ const props = defineProps<{
47
+ label?: string
48
+ description?: string
49
+ error?: string
50
+ forId?: string
51
+ variant?: TextfieldVariants["variant"]
52
+ size?: TextfieldVariants["size"]
53
+ iconSize?: TextfieldIconVariants["size"]
54
+ modelValue?: string
55
+ type?: string
56
+ placeholder?: string
57
+ disabled?: boolean
58
+ class?: string
59
+ prependIcon?: any
60
+ prependInnerIcon?: any
61
+ appendInnerIcon?: any
62
+ appendIcon?: any
63
+ }>()
64
+
65
+ const emit = defineEmits<{
66
+ (e: "update:modelValue", value: string): void
67
+ }>()
68
+
69
+ function onInput(event: Event) {
70
+ const target = event.target as HTMLInputElement
71
+ emit("update:modelValue", target.value)
72
+ }
73
+
74
+ // Compute padding classes for input based on inner icon presence
75
+ const getInputPaddingClass = () => {
76
+ const padding = []
77
+ if (props.prependInnerIcon) {
78
+ padding.push("ps-1.5")
79
+ } else {
80
+ padding.push("ps-[var(--tf-padding-x)]")
81
+ }
82
+ if (props.appendInnerIcon) {
83
+ padding.push("pe-1.5")
84
+ } else {
85
+ padding.push("pe-[var(--tf-padding-x)]")
86
+ }
87
+ return padding.join(" ")
88
+ }
89
+ </script>
90
+
91
+ <template>
92
+ <Field :class="props.class">
93
+ <FieldLabel v-if="label" :for="forId">{{ label }}</FieldLabel>
94
+ <FieldGroup class="flex flex-1 items-center">
95
+ <!-- Prepend icon (outside the textfield, to the left) -->
96
+ <component
97
+ v-if="prependIcon"
98
+ :is="prependIcon"
99
+ class="text-muted-foreground flex shrink-0 items-center"
100
+ :class="iconSize === 'sm' ? 'size-3.5' : iconSize === 'lg' ? 'size-5' : 'size-4'"
101
+ />
102
+ <!-- Textfield inner container -->
103
+ <div
104
+ :class="cn(
105
+ textfieldVariants({ variant: error ? 'error' : variant, size }),
106
+ 'flex flex-1 items-center focus-within:rounded-lg focus-within:ring-2 focus-within:ring-ring/50 dark:focus-within:[--ring:217.2_32.6%_50%] focus-within:[--ring:210_5%_80%]'
107
+ )"
108
+ >
109
+ <!-- Prepend-inner icon (inside, at start) -->
110
+ <component
111
+ v-if="prependInnerIcon"
112
+ :is="prependInnerIcon"
113
+ class="text-muted-foreground flex shrink-0 items-center"
114
+ :class="[
115
+ iconSize === 'sm' ? 'size-3.5' : iconSize === 'lg' ? 'size-5' : 'size-4',
116
+ 'me-1.5'
117
+ ]"
118
+ />
119
+ <input
120
+ :id="forId"
121
+ :type="type"
122
+ :value="modelValue"
123
+ :placeholder="placeholder"
124
+ :disabled="disabled"
125
+ :aria-invalid="!!error"
126
+ :class="cn(
127
+ 'flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 w-full min-w-0',
128
+ getInputPaddingClass()
129
+ )"
130
+ :style="{ '--tf-padding-x': '0.625rem' }"
131
+ @input="onInput"
132
+ />
133
+ <!-- Append-inner icon (inside, at end) -->
134
+ <component
135
+ v-if="appendInnerIcon"
136
+ :is="appendInnerIcon"
137
+ class="text-muted-foreground flex shrink-0 items-center"
138
+ :class="[
139
+ iconSize === 'sm' ? 'size-3.5' : iconSize === 'lg' ? 'size-5' : 'size-4',
140
+ 'ms-1.5'
141
+ ]"
142
+ />
143
+ </div>
144
+ <!-- Append icon (outside the textfield, to the right) -->
145
+ <component
146
+ v-if="appendIcon"
147
+ :is="appendIcon"
148
+ class="text-muted-foreground flex shrink-0 items-center"
149
+ :class="iconSize === 'sm' ? 'size-3.5' : iconSize === 'lg' ? 'size-5' : 'size-4'"
150
+ />
151
+ </FieldGroup>
152
+ <FieldDescription v-if="description && !error">{{ description }}</FieldDescription>
153
+ <p v-if="error" class="text-[0.8rem] font-medium text-destructive">
154
+ {{ error }}
155
+ </p>
156
+ </Field>
157
+ </template>