@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.
- package/README.md +105 -0
- package/docs/components/alert-dialog.md +32 -0
- package/docs/components/avatar.md +14 -0
- package/docs/components/badge.md +24 -0
- package/docs/components/button.md +69 -0
- package/docs/components/card.md +49 -0
- package/docs/components/dialog.md +46 -0
- package/docs/components/dropdown-menu.md +32 -0
- package/docs/components/index.md +69 -50
- package/docs/components/input.md +34 -0
- package/docs/components/remaining-components.md +253 -0
- package/docs/components/scroll-area.md +17 -0
- package/docs/components/select.md +31 -0
- package/docs/components/separator.md +14 -0
- package/docs/components/sheet.md +32 -0
- package/docs/components/skeleton.md +20 -0
- package/docs/components/table.md +33 -0
- package/docs/components/tabs.md +23 -0
- package/docs/layouts/dashboard.md +70 -0
- package/docs/layouts/detail-page.md +83 -0
- package/docs/layouts/index.md +37 -24
- package/docs/layouts/list-page.md +107 -0
- package/docs/layouts/settings-page.md +79 -0
- package/docs/layouts/step-wizard.md +73 -0
- package/package.json +7 -3
- package/src/components/ui/accordion/Accordion.vue +13 -0
- package/src/components/ui/accordion/AccordionContent.vue +20 -0
- package/src/components/ui/accordion/AccordionItem.vue +15 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +25 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert/Alert.vue +38 -0
- package/src/components/ui/alert/AlertDescription.vue +12 -0
- package/src/components/ui/alert/AlertTitle.vue +12 -0
- package/src/components/ui/alert/index.ts +3 -0
- package/src/components/ui/alert-dialog/AlertDialog.vue +13 -0
- package/src/components/ui/alert-dialog/AlertDialogAction.vue +21 -0
- package/src/components/ui/alert-dialog/AlertDialogCancel.vue +21 -0
- package/src/components/ui/alert-dialog/AlertDialogContent.vue +39 -0
- package/src/components/ui/alert-dialog/AlertDialogDescription.vue +15 -0
- package/src/components/ui/alert-dialog/AlertDialogFooter.vue +12 -0
- package/src/components/ui/alert-dialog/AlertDialogHeader.vue +12 -0
- package/src/components/ui/alert-dialog/AlertDialogTitle.vue +15 -0
- package/src/components/ui/alert-dialog/AlertDialogTrigger.vue +11 -0
- package/src/components/ui/alert-dialog/index.ts +9 -0
- package/src/components/ui/breadcrumb/Breadcrumb.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +12 -0
- package/src/components/ui/breadcrumb/BreadcrumbItem.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbLink.vue +20 -0
- package/src/components/ui/breadcrumb/BreadcrumbList.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbPage.vue +6 -0
- package/src/components/ui/breadcrumb/BreadcrumbSeparator.vue +11 -0
- package/src/components/ui/breadcrumb/index.ts +7 -0
- package/src/components/ui/button/Button.vue +0 -1
- package/src/components/ui/checkbox/Checkbox.vue +25 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +13 -0
- package/src/components/ui/collapsible/index.ts +2 -0
- package/src/components/ui/command/Command.vue +16 -0
- package/src/components/ui/command/CommandEmpty.vue +5 -0
- package/src/components/ui/command/CommandGroup.vue +22 -0
- package/src/components/ui/command/CommandInput.vue +21 -0
- package/src/components/ui/command/CommandItem.vue +22 -0
- package/src/components/ui/command/CommandList.vue +17 -0
- package/src/components/ui/command/CommandSeparator.vue +5 -0
- package/src/components/ui/command/index.ts +7 -0
- package/src/components/ui/context-menu/ContextMenuContent.vue +24 -0
- package/src/components/ui/context-menu/ContextMenuItem.vue +16 -0
- package/src/components/ui/context-menu/ContextMenuLabel.vue +9 -0
- package/src/components/ui/context-menu/ContextMenuSeparator.vue +9 -0
- package/src/components/ui/context-menu/ContextMenuSubContent.vue +14 -0
- package/src/components/ui/context-menu/index.ts +9 -0
- package/src/components/ui/dialog/Dialog.vue +14 -0
- package/src/components/ui/dialog/DialogClose.vue +12 -0
- package/src/components/ui/dialog/DialogContent.vue +48 -0
- package/src/components/ui/dialog/DialogDescription.vue +23 -0
- package/src/components/ui/dialog/DialogFooter.vue +12 -0
- package/src/components/ui/dialog/DialogHeader.vue +12 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +47 -0
- package/src/components/ui/dialog/DialogTitle.vue +23 -0
- package/src/components/ui/dialog/DialogTrigger.vue +12 -0
- package/src/components/ui/dialog/index.ts +9 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +28 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +33 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +12 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +13 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +23 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +11 -0
- package/src/components/ui/dropdown-menu/index.ts +14 -0
- package/src/components/ui/form/FormControl.vue +3 -0
- package/src/components/ui/form/FormDescription.vue +6 -0
- package/src/components/ui/form/FormItem.vue +6 -0
- package/src/components/ui/form/FormLabel.vue +10 -0
- package/src/components/ui/form/FormMessage.vue +10 -0
- package/src/components/ui/form/index.ts +9 -0
- package/src/components/ui/hover-card/HoverCard.vue +13 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +26 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/label/Label.vue +18 -0
- package/src/components/ui/label/index.ts +1 -0
- package/src/components/ui/menubar/MenubarContent.vue +15 -0
- package/src/components/ui/menubar/MenubarItem.vue +13 -0
- package/src/components/ui/menubar/MenubarTrigger.vue +13 -0
- package/src/components/ui/menubar/index.ts +5 -0
- package/src/components/ui/navigation-menu/NavigationMenuContent.vue +14 -0
- package/src/components/ui/navigation-menu/NavigationMenuTrigger.vue +15 -0
- package/src/components/ui/navigation-menu/index.ts +4 -0
- package/src/components/ui/pagination/PaginationContent.vue +13 -0
- package/src/components/ui/pagination/PaginationEllipsis.vue +12 -0
- package/src/components/ui/pagination/PaginationNext.vue +14 -0
- package/src/components/ui/pagination/PaginationPrev.vue +14 -0
- package/src/components/ui/pagination/index.ts +6 -0
- package/src/components/ui/popover/Popover.vue +13 -0
- package/src/components/ui/popover/PopoverContent.vue +27 -0
- package/src/components/ui/popover/PopoverTrigger.vue +11 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/progress/Progress.vue +21 -0
- package/src/components/ui/progress/index.ts +1 -0
- package/src/components/ui/radio-group/RadioGroup.vue +16 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +24 -0
- package/src/components/ui/radio-group/index.ts +2 -0
- package/src/components/ui/select/Select.vue +13 -0
- package/src/components/ui/select/SelectContent.vue +40 -0
- package/src/components/ui/select/SelectGroup.vue +15 -0
- package/src/components/ui/select/SelectItem.vue +30 -0
- package/src/components/ui/select/SelectLabel.vue +15 -0
- package/src/components/ui/select/SelectSeparator.vue +13 -0
- package/src/components/ui/select/SelectTrigger.vue +23 -0
- package/src/components/ui/select/SelectValue.vue +11 -0
- package/src/components/ui/select/index.ts +8 -0
- package/src/components/ui/sheet/Sheet.vue +13 -0
- package/src/components/ui/sheet/SheetClose.vue +11 -0
- package/src/components/ui/sheet/SheetContent.vue +65 -0
- package/src/components/ui/sheet/SheetDescription.vue +15 -0
- package/src/components/ui/sheet/SheetFooter.vue +12 -0
- package/src/components/ui/sheet/SheetHeader.vue +12 -0
- package/src/components/ui/sheet/SheetTitle.vue +15 -0
- package/src/components/ui/sheet/SheetTrigger.vue +11 -0
- package/src/components/ui/sheet/index.ts +8 -0
- package/src/components/ui/slider/Slider.vue +26 -0
- package/src/components/ui/slider/index.ts +1 -0
- package/src/components/ui/switch/Switch.vue +24 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +13 -0
- package/src/components/ui/table/TableBody.vue +6 -0
- package/src/components/ui/table/TableCaption.vue +6 -0
- package/src/components/ui/table/TableCell.vue +6 -0
- package/src/components/ui/table/TableFooter.vue +6 -0
- package/src/components/ui/table/TableHead.vue +6 -0
- package/src/components/ui/table/TableHeader.vue +6 -0
- package/src/components/ui/table/TableRow.vue +6 -0
- package/src/components/ui/table/index.ts +8 -0
- package/src/components/ui/tabs/Tabs.vue +13 -0
- package/src/components/ui/tabs/TabsContent.vue +21 -0
- package/src/components/ui/tabs/TabsList.vue +21 -0
- package/src/components/ui/tabs/TabsTrigger.vue +21 -0
- package/src/components/ui/tabs/index.ts +4 -0
- package/src/components/ui/textarea/Textarea.vue +29 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/toggle/Toggle.vue +40 -0
- package/src/components/ui/toggle/index.ts +1 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +16 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +21 -0
- package/src/components/ui/toggle-group/index.ts +2 -0
- package/src/components/ui/tooltip/Tooltip.vue +13 -0
- package/src/components/ui/tooltip/TooltipContent.vue +27 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +12 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/eslint/index.js +192 -0
- package/src/eslint/recommended.js +64 -0
- package/src/index.ts +46 -192
- package/src/patterns/DetailView.vue +2 -2
- package/src/patterns/EmptyState.vue +2 -2
- package/src/patterns/FormView.vue +2 -2
- package/src/patterns/ListView.vue +2 -2
- 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,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,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,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
|