@reinvented/design 0.2.0 → 0.3.0

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 (185) hide show
  1. package/README.md +105 -0
  2. package/docs/components/alert-dialog.md +32 -0
  3. package/docs/components/avatar.md +14 -0
  4. package/docs/components/badge.md +24 -0
  5. package/docs/components/button.md +69 -0
  6. package/docs/components/card.md +49 -0
  7. package/docs/components/dialog.md +46 -0
  8. package/docs/components/dropdown-menu.md +32 -0
  9. package/docs/components/index.md +69 -50
  10. package/docs/components/input.md +34 -0
  11. package/docs/components/remaining-components.md +253 -0
  12. package/docs/components/scroll-area.md +17 -0
  13. package/docs/components/select.md +31 -0
  14. package/docs/components/separator.md +14 -0
  15. package/docs/components/sheet.md +32 -0
  16. package/docs/components/skeleton.md +20 -0
  17. package/docs/components/table.md +33 -0
  18. package/docs/components/tabs.md +23 -0
  19. package/docs/layouts/dashboard.md +70 -0
  20. package/docs/layouts/detail-page.md +83 -0
  21. package/docs/layouts/index.md +37 -24
  22. package/docs/layouts/list-page.md +107 -0
  23. package/docs/layouts/settings-page.md +79 -0
  24. package/docs/layouts/step-wizard.md +73 -0
  25. package/package.json +7 -3
  26. package/src/components/ui/accordion/Accordion.vue +13 -0
  27. package/src/components/ui/accordion/AccordionContent.vue +20 -0
  28. package/src/components/ui/accordion/AccordionItem.vue +15 -0
  29. package/src/components/ui/accordion/AccordionTrigger.vue +25 -0
  30. package/src/components/ui/accordion/index.ts +4 -0
  31. package/src/components/ui/alert/Alert.vue +38 -0
  32. package/src/components/ui/alert/AlertDescription.vue +12 -0
  33. package/src/components/ui/alert/AlertTitle.vue +12 -0
  34. package/src/components/ui/alert/index.ts +3 -0
  35. package/src/components/ui/alert-dialog/AlertDialog.vue +13 -0
  36. package/src/components/ui/alert-dialog/AlertDialogAction.vue +21 -0
  37. package/src/components/ui/alert-dialog/AlertDialogCancel.vue +21 -0
  38. package/src/components/ui/alert-dialog/AlertDialogContent.vue +39 -0
  39. package/src/components/ui/alert-dialog/AlertDialogDescription.vue +15 -0
  40. package/src/components/ui/alert-dialog/AlertDialogFooter.vue +12 -0
  41. package/src/components/ui/alert-dialog/AlertDialogHeader.vue +12 -0
  42. package/src/components/ui/alert-dialog/AlertDialogTitle.vue +15 -0
  43. package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +11 -0
  44. package/src/components/ui/alert-dialog/index.ts +9 -0
  45. package/src/components/ui/breadcrumb/Breadcrumb.vue +6 -0
  46. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +12 -0
  47. package/src/components/ui/breadcrumb/BreadcrumbItem.vue +6 -0
  48. package/src/components/ui/breadcrumb/BreadcrumbLink.vue +20 -0
  49. package/src/components/ui/breadcrumb/BreadcrumbList.vue +6 -0
  50. package/src/components/ui/breadcrumb/BreadcrumbPage.vue +6 -0
  51. package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +11 -0
  52. package/src/components/ui/breadcrumb/index.ts +7 -0
  53. package/src/components/ui/button/Button.vue +0 -1
  54. package/src/components/ui/checkbox/Checkbox.vue +25 -0
  55. package/src/components/ui/checkbox/index.ts +1 -0
  56. package/src/components/ui/collapsible/Collapsible.vue +13 -0
  57. package/src/components/ui/collapsible/index.ts +2 -0
  58. package/src/components/ui/command/Command.vue +16 -0
  59. package/src/components/ui/command/CommandEmpty.vue +5 -0
  60. package/src/components/ui/command/CommandGroup.vue +22 -0
  61. package/src/components/ui/command/CommandInput.vue +21 -0
  62. package/src/components/ui/command/CommandItem.vue +22 -0
  63. package/src/components/ui/command/CommandList.vue +17 -0
  64. package/src/components/ui/command/CommandSeparator.vue +5 -0
  65. package/src/components/ui/command/index.ts +7 -0
  66. package/src/components/ui/context-menu/ContextMenuContent.vue +24 -0
  67. package/src/components/ui/context-menu/ContextMenuItem.vue +16 -0
  68. package/src/components/ui/context-menu/ContextMenuLabel.vue +9 -0
  69. package/src/components/ui/context-menu/ContextMenuSeparator.vue +9 -0
  70. package/src/components/ui/context-menu/ContextMenuSubContent.vue +14 -0
  71. package/src/components/ui/context-menu/index.ts +9 -0
  72. package/src/components/ui/dialog/Dialog.vue +14 -0
  73. package/src/components/ui/dialog/DialogClose.vue +12 -0
  74. package/src/components/ui/dialog/DialogContent.vue +48 -0
  75. package/src/components/ui/dialog/DialogDescription.vue +23 -0
  76. package/src/components/ui/dialog/DialogFooter.vue +12 -0
  77. package/src/components/ui/dialog/DialogHeader.vue +12 -0
  78. package/src/components/ui/dialog/DialogScrollContent.vue +47 -0
  79. package/src/components/ui/dialog/DialogTitle.vue +23 -0
  80. package/src/components/ui/dialog/DialogTrigger.vue +12 -0
  81. package/src/components/ui/dialog/index.ts +9 -0
  82. package/src/components/ui/dropdown-menu/DropdownMenu.vue +13 -0
  83. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +28 -0
  84. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +33 -0
  85. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  86. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +27 -0
  87. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
  88. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +13 -0
  89. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +27 -0
  90. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +13 -0
  91. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +12 -0
  92. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +13 -0
  93. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
  94. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +23 -0
  95. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +11 -0
  96. package/src/components/ui/dropdown-menu/index.ts +14 -0
  97. package/src/components/ui/form/FormControl.vue +3 -0
  98. package/src/components/ui/form/FormDescription.vue +6 -0
  99. package/src/components/ui/form/FormItem.vue +6 -0
  100. package/src/components/ui/form/FormLabel.vue +10 -0
  101. package/src/components/ui/form/FormMessage.vue +10 -0
  102. package/src/components/ui/form/index.ts +9 -0
  103. package/src/components/ui/hover-card/HoverCard.vue +13 -0
  104. package/src/components/ui/hover-card/HoverCardContent.vue +26 -0
  105. package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
  106. package/src/components/ui/hover-card/index.ts +3 -0
  107. package/src/components/ui/label/Label.vue +18 -0
  108. package/src/components/ui/label/index.ts +1 -0
  109. package/src/components/ui/menubar/MenubarContent.vue +15 -0
  110. package/src/components/ui/menubar/MenubarItem.vue +13 -0
  111. package/src/components/ui/menubar/MenubarTrigger.vue +13 -0
  112. package/src/components/ui/menubar/index.ts +5 -0
  113. package/src/components/ui/navigation-menu/NavigationMenuContent.vue +14 -0
  114. package/src/components/ui/navigation-menu/NavigationMenuTrigger.vue +15 -0
  115. package/src/components/ui/navigation-menu/index.ts +4 -0
  116. package/src/components/ui/pagination/PaginationContent.vue +13 -0
  117. package/src/components/ui/pagination/PaginationEllipsis.vue +12 -0
  118. package/src/components/ui/pagination/PaginationNext.vue +14 -0
  119. package/src/components/ui/pagination/PaginationPrev.vue +14 -0
  120. package/src/components/ui/pagination/index.ts +6 -0
  121. package/src/components/ui/popover/Popover.vue +13 -0
  122. package/src/components/ui/popover/PopoverContent.vue +27 -0
  123. package/src/components/ui/popover/PopoverTrigger.vue +11 -0
  124. package/src/components/ui/popover/index.ts +3 -0
  125. package/src/components/ui/progress/Progress.vue +21 -0
  126. package/src/components/ui/progress/index.ts +1 -0
  127. package/src/components/ui/radio-group/RadioGroup.vue +16 -0
  128. package/src/components/ui/radio-group/RadioGroupItem.vue +24 -0
  129. package/src/components/ui/radio-group/index.ts +2 -0
  130. package/src/components/ui/select/Select.vue +13 -0
  131. package/src/components/ui/select/SelectContent.vue +40 -0
  132. package/src/components/ui/select/SelectGroup.vue +15 -0
  133. package/src/components/ui/select/SelectItem.vue +30 -0
  134. package/src/components/ui/select/SelectLabel.vue +15 -0
  135. package/src/components/ui/select/SelectSeparator.vue +13 -0
  136. package/src/components/ui/select/SelectTrigger.vue +23 -0
  137. package/src/components/ui/select/SelectValue.vue +11 -0
  138. package/src/components/ui/select/index.ts +8 -0
  139. package/src/components/ui/sheet/Sheet.vue +13 -0
  140. package/src/components/ui/sheet/SheetClose.vue +11 -0
  141. package/src/components/ui/sheet/SheetContent.vue +65 -0
  142. package/src/components/ui/sheet/SheetDescription.vue +15 -0
  143. package/src/components/ui/sheet/SheetFooter.vue +12 -0
  144. package/src/components/ui/sheet/SheetHeader.vue +12 -0
  145. package/src/components/ui/sheet/SheetTitle.vue +15 -0
  146. package/src/components/ui/sheet/SheetTrigger.vue +11 -0
  147. package/src/components/ui/sheet/index.ts +8 -0
  148. package/src/components/ui/slider/Slider.vue +26 -0
  149. package/src/components/ui/slider/index.ts +1 -0
  150. package/src/components/ui/switch/Switch.vue +24 -0
  151. package/src/components/ui/switch/index.ts +1 -0
  152. package/src/components/ui/table/Table.vue +13 -0
  153. package/src/components/ui/table/TableBody.vue +6 -0
  154. package/src/components/ui/table/TableCaption.vue +6 -0
  155. package/src/components/ui/table/TableCell.vue +6 -0
  156. package/src/components/ui/table/TableFooter.vue +6 -0
  157. package/src/components/ui/table/TableHead.vue +6 -0
  158. package/src/components/ui/table/TableHeader.vue +6 -0
  159. package/src/components/ui/table/TableRow.vue +6 -0
  160. package/src/components/ui/table/index.ts +8 -0
  161. package/src/components/ui/tabs/Tabs.vue +13 -0
  162. package/src/components/ui/tabs/TabsContent.vue +21 -0
  163. package/src/components/ui/tabs/TabsList.vue +21 -0
  164. package/src/components/ui/tabs/TabsTrigger.vue +21 -0
  165. package/src/components/ui/tabs/index.ts +4 -0
  166. package/src/components/ui/textarea/Textarea.vue +29 -0
  167. package/src/components/ui/textarea/index.ts +1 -0
  168. package/src/components/ui/toggle/Toggle.vue +40 -0
  169. package/src/components/ui/toggle/index.ts +1 -0
  170. package/src/components/ui/toggle-group/ToggleGroup.vue +16 -0
  171. package/src/components/ui/toggle-group/ToggleGroupItem.vue +21 -0
  172. package/src/components/ui/toggle-group/index.ts +2 -0
  173. package/src/components/ui/tooltip/Tooltip.vue +13 -0
  174. package/src/components/ui/tooltip/TooltipContent.vue +27 -0
  175. package/src/components/ui/tooltip/TooltipProvider.vue +12 -0
  176. package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
  177. package/src/components/ui/tooltip/index.ts +4 -0
  178. package/src/eslint/index.js +192 -0
  179. package/src/eslint/recommended.js +64 -0
  180. package/src/index.ts +46 -192
  181. package/src/patterns/DetailView.vue +2 -2
  182. package/src/patterns/EmptyState.vue +2 -2
  183. package/src/patterns/FormView.vue +2 -2
  184. package/src/patterns/ListView.vue +2 -2
  185. package/tsconfig.json +17 -3
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'radix-vue'
4
+ import { cn } from '../lib/utils'
5
+
6
+ const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
7
+ const delegatedProps = computed(() => { const { class: _, ...d } = props; return d })
8
+ const forwardedProps = useForwardProps(delegatedProps)
9
+ </script>
10
+
11
+ <template>
12
+ <TabsTrigger
13
+ v-bind="forwardedProps"
14
+ :class="cn(
15
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
16
+ props.class,
17
+ )"
18
+ >
19
+ <slot />
20
+ </TabsTrigger>
21
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Tabs } from './Tabs.vue'
2
+ export { default as TabsContent } from './TabsContent.vue'
3
+ export { default as TabsList } from './TabsList.vue'
4
+ export { default as TabsTrigger } from './TabsTrigger.vue'
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { cn } from '../lib/utils'
4
+
5
+ const props = defineProps<{
6
+ class?: HTMLAttributes['class']
7
+ defaultValue?: string
8
+ modelValue?: string
9
+ }>()
10
+
11
+ const emits = defineEmits<{
12
+ 'update:modelValue': [value: string]
13
+ }>()
14
+
15
+ const modelValue = computed({
16
+ get: () => props.modelValue,
17
+ set: (val) => emits('update:modelValue', val ?? ''),
18
+ })
19
+ </script>
20
+
21
+ <template>
22
+ <textarea
23
+ v-model="modelValue"
24
+ :class="cn(
25
+ 'flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
26
+ props.class,
27
+ )"
28
+ />
29
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Textarea } from './Textarea.vue'
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { Toggle as ToggleRoot, type ToggleEmits, type ToggleProps, useForwardPropsEmits } from 'radix-vue'
4
+ import { cva, type VariantProps } from 'class-variance-authority'
5
+ import { cn } from '../lib/utils'
6
+
7
+ const toggleVariants = cva(
8
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-transparent',
13
+ outline: 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
14
+ },
15
+ size: {
16
+ default: 'h-9 px-3',
17
+ sm: 'h-8 px-2',
18
+ lg: 'h-10 px-3',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: 'default',
23
+ size: 'default',
24
+ },
25
+ },
26
+ )
27
+
28
+ type ToggleVariants = VariantProps<typeof toggleVariants>
29
+
30
+ const props = defineProps<ToggleProps & { class?: HTMLAttributes['class']; variant?: ToggleVariants['variant']; size?: ToggleVariants['size'] }>()
31
+ const emits = defineEmits<ToggleEmits>()
32
+ const delegatedProps = computed(() => { const { class: _, variant: _v, size: _s, ...d } = props; return d })
33
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
34
+ </script>
35
+
36
+ <template>
37
+ <ToggleRoot v-bind="forwarded" :class="cn(toggleVariants({ variant, size }), props.class)">
38
+ <slot />
39
+ </ToggleRoot>
40
+ </template>
@@ -0,0 +1 @@
1
+ export { default as Toggle } from './Toggle.vue'
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue'
4
+ import { cn } from '../lib/utils'
5
+
6
+ const props = defineProps<ToggleGroupRootProps & { class?: HTMLAttributes['class'] }>()
7
+ const emits = defineEmits<ToggleGroupRootEmits>()
8
+ const delegatedProps = computed(() => { const { class: _, ...d } = props; return d })
9
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
10
+ </script>
11
+
12
+ <template>
13
+ <ToggleGroupRoot v-bind="forwarded" :class="cn('flex items-center justify-center gap-1', props.class)">
14
+ <slot />
15
+ </ToggleGroupRoot>
16
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue'
4
+ import { cn } from '../lib/utils'
5
+
6
+ const props = defineProps<ToggleGroupItemProps & { class?: HTMLAttributes['class'] }>()
7
+ const delegatedProps = computed(() => { const { class: _, ...d } = props; return d })
8
+ const forwardedProps = useForwardProps(delegatedProps)
9
+ </script>
10
+
11
+ <template>
12
+ <ToggleGroupItem
13
+ v-bind="forwardedProps"
14
+ :class="cn(
15
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground bg-transparent h-9 px-3',
16
+ props.class,
17
+ )"
18
+ >
19
+ <slot />
20
+ </ToggleGroupItem>
21
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as ToggleGroup } from './ToggleGroup.vue'
2
+ export { default as ToggleGroupItem } from './ToggleGroupItem.vue'
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ import { TooltipRoot, type TooltipRootEmits, type TooltipRootProps, useForwardPropsEmits } from 'radix-vue'
3
+
4
+ const props = defineProps<TooltipRootProps>()
5
+ const emits = defineEmits<TooltipRootEmits>()
6
+ const forwarded = useForwardPropsEmits(props, emits)
7
+ </script>
8
+
9
+ <template>
10
+ <TooltipRoot v-bind="forwarded">
11
+ <slot />
12
+ </TooltipRoot>
13
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { type HTMLAttributes, computed } from 'vue'
3
+ import { TooltipContent, type TooltipContentEmits, type TooltipContentProps, TooltipPortal, useForwardPropsEmits } from 'radix-vue'
4
+ import { cn } from '../lib/utils'
5
+
6
+ const props = withDefaults(
7
+ defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(),
8
+ { sideOffset: 4 },
9
+ )
10
+ const emits = defineEmits<TooltipContentEmits>()
11
+ const delegatedProps = computed(() => { const { class: _, ...d } = props; return d })
12
+ const forwarded = useForwardPropsEmits(delegatedProps, emits)
13
+ </script>
14
+
15
+ <template>
16
+ <TooltipPortal>
17
+ <TooltipContent
18
+ v-bind="forwarded"
19
+ :class="cn(
20
+ 'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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',
21
+ props.class,
22
+ )"
23
+ >
24
+ <slot />
25
+ </TooltipContent>
26
+ </TooltipPortal>
27
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ import { TooltipProvider, type TooltipProviderProps, useForwardProps } from 'radix-vue'
3
+
4
+ const props = withDefaults(defineProps<TooltipProviderProps>(), { delayDuration: 200 })
5
+ const forwardedProps = useForwardProps(props)
6
+ </script>
7
+
8
+ <template>
9
+ <TooltipProvider v-bind="forwardedProps">
10
+ <slot />
11
+ </TooltipProvider>
12
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import { TooltipTrigger, type TooltipTriggerProps } from 'radix-vue'
3
+
4
+ const props = defineProps<TooltipTriggerProps>()
5
+ </script>
6
+
7
+ <template>
8
+ <TooltipTrigger v-bind="props">
9
+ <slot />
10
+ </TooltipTrigger>
11
+ </template>
@@ -0,0 +1,4 @@
1
+ export { default as Tooltip } from './Tooltip.vue'
2
+ export { default as TooltipContent } from './TooltipContent.vue'
3
+ export { default as TooltipProvider } from './TooltipProvider.vue'
4
+ export { default as TooltipTrigger } from './TooltipTrigger.vue'
@@ -0,0 +1,192 @@
1
+ /**
2
+ * @reinvented/design ESLint Plugin
3
+ *
4
+ * Enforces the Reinvented design system rules across all consumer apps.
5
+ * See docs/rules.md for the full list.
6
+ *
7
+ * Usage (flat config):
8
+ * import recommended from '@reinvented/design/eslint/recommended'
9
+ * export default [...recommended]
10
+ */
11
+
12
+ // ── Helpers ────────────────────────────────────────────────────────
13
+
14
+ /** Matches emoji characters (Emoji_Presentation + common ranges) */
15
+ const EMOJI_REGEX = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}\u{231A}-\u{231B}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2614}-\u{2615}\u{2648}-\u{2653}\u{267F}\u{2693}\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}]/u
16
+
17
+ /**
18
+ * Creates a rule that bans a specific raw HTML element in Vue templates.
19
+ * @param {string} element - The raw element name to ban (e.g. 'button')
20
+ * @param {string} component - The DS component to use instead (e.g. 'Button')
21
+ */
22
+ function createNoRawElementRule(element, component) {
23
+ return {
24
+ meta: {
25
+ type: 'suggestion',
26
+ docs: {
27
+ description: `Disallow raw <${element}> — use <${component}> from @reinvented/design`,
28
+ recommended: true,
29
+ },
30
+ messages: {
31
+ noRawElement: `Use <${component}> from @reinvented/design instead of raw <${element}>. See docs/rules.md.`,
32
+ },
33
+ schema: [],
34
+ },
35
+ create(context) {
36
+ // Works with eslint-plugin-vue's template AST
37
+ return context.parserServices?.defineTemplateBodyVisitor
38
+ ? context.parserServices.defineTemplateBodyVisitor({
39
+ VElement(node) {
40
+ if (node.rawName === element) {
41
+ context.report({ node, messageId: 'noRawElement' })
42
+ }
43
+ },
44
+ })
45
+ : {}
46
+ },
47
+ }
48
+ }
49
+
50
+ // ── Rules ──────────────────────────────────────────────────────────
51
+
52
+ const rules = {
53
+ // Rule 1-4: No raw form elements
54
+ 'no-raw-button': createNoRawElementRule('button', 'Button'),
55
+ 'no-raw-input': createNoRawElementRule('input', 'Input'),
56
+ 'no-raw-select': createNoRawElementRule('select', 'Select'),
57
+ 'no-raw-textarea': createNoRawElementRule('textarea', 'Textarea'),
58
+
59
+ // Rule 5: No browser dialog APIs
60
+ 'no-browser-dialogs': {
61
+ meta: {
62
+ type: 'problem',
63
+ docs: {
64
+ description: 'Disallow alert(), confirm(), prompt() — use Dialog/AlertDialog/Toast from @reinvented/design',
65
+ recommended: true,
66
+ },
67
+ messages: {
68
+ noBrowserDialog: 'Use Dialog, AlertDialog, or Toast from @reinvented/design instead of {{ name }}(). See docs/rules.md.',
69
+ },
70
+ schema: [],
71
+ },
72
+ create(context) {
73
+ const banned = new Set(['alert', 'confirm', 'prompt'])
74
+ return {
75
+ CallExpression(node) {
76
+ // Direct calls: alert(), confirm(), prompt()
77
+ if (node.callee.type === 'Identifier' && banned.has(node.callee.name)) {
78
+ context.report({ node, messageId: 'noBrowserDialog', data: { name: node.callee.name } })
79
+ }
80
+ // window.alert(), window.confirm(), window.prompt()
81
+ if (
82
+ node.callee.type === 'MemberExpression' &&
83
+ node.callee.object.type === 'Identifier' &&
84
+ node.callee.object.name === 'window' &&
85
+ node.callee.property.type === 'Identifier' &&
86
+ banned.has(node.callee.property.name)
87
+ ) {
88
+ context.report({ node, messageId: 'noBrowserDialog', data: { name: node.callee.property.name } })
89
+ }
90
+ },
91
+ }
92
+ },
93
+ },
94
+
95
+ // Rule 10: No emojis in templates
96
+ 'no-emoji': {
97
+ meta: {
98
+ type: 'suggestion',
99
+ docs: {
100
+ description: 'Disallow emoji characters in Vue templates — use Lucide icons from lucide-vue-next',
101
+ recommended: true,
102
+ },
103
+ messages: {
104
+ noEmoji: 'Use a Lucide icon from lucide-vue-next instead of emoji characters. See docs/rules.md.',
105
+ },
106
+ schema: [],
107
+ },
108
+ create(context) {
109
+ return context.parserServices?.defineTemplateBodyVisitor
110
+ ? context.parserServices.defineTemplateBodyVisitor({
111
+ 'VText'(node) {
112
+ if (EMOJI_REGEX.test(node.value)) {
113
+ context.report({ node, messageId: 'noEmoji' })
114
+ }
115
+ },
116
+ })
117
+ : {}
118
+ },
119
+ },
120
+
121
+ // Rule 6: No hardcoded colors
122
+ 'no-hardcoded-colors': {
123
+ meta: {
124
+ type: 'suggestion',
125
+ docs: {
126
+ description: 'Disallow hardcoded color values in inline styles — use CSS variables via Tailwind tokens',
127
+ recommended: true,
128
+ },
129
+ messages: {
130
+ noHardcodedColor: 'Use design token CSS variables (e.g. bg-background, text-foreground) instead of hardcoded colors. See docs/rules.md.',
131
+ },
132
+ schema: [],
133
+ },
134
+ create(context) {
135
+ const COLOR_REGEX = /#[0-9a-fA-F]{3,8}\b|rgba?\s*\(|hsla?\s*\(/
136
+ return context.parserServices?.defineTemplateBodyVisitor
137
+ ? context.parserServices.defineTemplateBodyVisitor({
138
+ 'VAttribute[key.name="style"]'(node) {
139
+ const value = node.value?.value
140
+ if (value && COLOR_REGEX.test(value)) {
141
+ context.report({ node, messageId: 'noHardcodedColor' })
142
+ }
143
+ },
144
+ })
145
+ : {}
146
+ },
147
+ },
148
+
149
+ // Rule 7: No arbitrary Tailwind values
150
+ 'no-arbitrary-tailwind': {
151
+ meta: {
152
+ type: 'suggestion',
153
+ docs: {
154
+ description: 'Disallow arbitrary Tailwind values like text-[#333] or p-[13px] — use token-based utilities',
155
+ recommended: true,
156
+ },
157
+ messages: {
158
+ noArbitrary: 'Use token-based Tailwind utilities instead of arbitrary values (e.g. {{ value }}). See docs/rules.md.',
159
+ },
160
+ schema: [],
161
+ },
162
+ create(context) {
163
+ // Matches Tailwind arbitrary value syntax: word-[value]
164
+ const ARBITRARY_REGEX = /\b(?:text|bg|border|ring|shadow|p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|w|h|gap|space|rounded|top|right|bottom|left|inset|min-w|min-h|max-w|max-h)-\[.+?\]/
165
+ return context.parserServices?.defineTemplateBodyVisitor
166
+ ? context.parserServices.defineTemplateBodyVisitor({
167
+ 'VAttribute[key.name="class"]'(node) {
168
+ const value = node.value?.value
169
+ if (value) {
170
+ const match = value.match(ARBITRARY_REGEX)
171
+ if (match) {
172
+ context.report({ node, messageId: 'noArbitrary', data: { value: match[0] } })
173
+ }
174
+ }
175
+ },
176
+ })
177
+ : {}
178
+ },
179
+ },
180
+ }
181
+
182
+ // ── Plugin Export ──────────────────────────────────────────────────
183
+
184
+ const plugin = {
185
+ meta: {
186
+ name: '@reinvented/design',
187
+ version: '0.3.0',
188
+ },
189
+ rules,
190
+ }
191
+
192
+ export default plugin
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @reinvented/design — Recommended ESLint Flat Config
3
+ *
4
+ * Usage:
5
+ * import { recommended } from '@reinvented/design/eslint/recommended'
6
+ * export default [...recommended]
7
+ *
8
+ * Or combine with your own config:
9
+ * export default [...recommended, { rules: { /* overrides */ } }]
10
+ */
11
+
12
+ import vuePlugin from 'eslint-plugin-vue'
13
+ import vueParser from 'vue-eslint-parser'
14
+ import designPlugin from './index.js'
15
+
16
+ const PREFIX = 'reinvented-design'
17
+
18
+ export const recommended = [
19
+ // ── Base Vue support (essential rules) ──────────────────────────
20
+ ...vuePlugin.configs['flat/essential'],
21
+
22
+ // ── Design system enforcement ───────────────────────────────────
23
+ {
24
+ name: '@reinvented/design/recommended',
25
+ files: ['**/*.vue'],
26
+ languageOptions: {
27
+ parser: vueParser,
28
+ },
29
+ plugins: {
30
+ [PREFIX]: designPlugin,
31
+ },
32
+ rules: {
33
+ // Component usage — error on raw HTML elements
34
+ [`${PREFIX}/no-raw-button`]: 'error',
35
+ [`${PREFIX}/no-raw-input`]: 'error',
36
+ [`${PREFIX}/no-raw-select`]: 'error',
37
+ [`${PREFIX}/no-raw-textarea`]: 'error',
38
+
39
+ // No browser dialogs
40
+ [`${PREFIX}/no-browser-dialogs`]: 'error',
41
+
42
+ // No emojis — use Lucide icons
43
+ [`${PREFIX}/no-emoji`]: 'error',
44
+
45
+ // Styling hygiene
46
+ [`${PREFIX}/no-hardcoded-colors`]: 'warn',
47
+ [`${PREFIX}/no-arbitrary-tailwind`]: 'warn',
48
+ },
49
+ },
50
+
51
+ // ── JS/TS files: ban browser dialogs everywhere ─────────────────
52
+ {
53
+ name: '@reinvented/design/recommended-scripts',
54
+ files: ['**/*.{js,ts,jsx,tsx,vue}'],
55
+ plugins: {
56
+ [PREFIX]: designPlugin,
57
+ },
58
+ rules: {
59
+ [`${PREFIX}/no-browser-dialogs`]: 'error',
60
+ },
61
+ },
62
+ ]
63
+
64
+ export default recommended