add elements components
This commit is contained in:
38
components/elements/Button.config.tsx
Normal file
38
components/elements/Button.config.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { ElementConfig } from "./types"
|
||||
|
||||
export const ButtonConfig: ElementConfig = {
|
||||
Button: {
|
||||
label: "Button",
|
||||
fields: {
|
||||
children: {
|
||||
type: "text",
|
||||
label: "Label",
|
||||
},
|
||||
variant: {
|
||||
type: "select",
|
||||
label: "Variant",
|
||||
options: [
|
||||
{ label: "Default", value: "default" },
|
||||
{ label: "Destructive", value: "destructive" },
|
||||
{ label: "Outline", value: "outline" },
|
||||
{ label: "Secondary", value: "secondary" },
|
||||
{ label: "Ghost", value: "ghost" },
|
||||
{ label: "Link", value: "link" },
|
||||
],
|
||||
},
|
||||
size: {
|
||||
type: "select",
|
||||
label: "Size",
|
||||
options: [
|
||||
{ label: "Default", value: "default" },
|
||||
{ label: "Extra Small", value: "xs" },
|
||||
{ label: "Small", value: "sm" },
|
||||
{ label: "Large", value: "lg" },
|
||||
{ label: "Icon", value: "icon" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default ButtonConfig
|
||||
26
components/elements/Button.tsx
Normal file
26
components/elements/Button.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from "react"
|
||||
|
||||
import {
|
||||
Button as ShadcnButton,
|
||||
type buttonVariants,
|
||||
} from "@/components/ui/button"
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
export type ButtonVariant = NonNullable<
|
||||
VariantProps<typeof buttonVariants>["variant"]
|
||||
>
|
||||
export type ButtonSize = NonNullable<
|
||||
VariantProps<typeof buttonVariants>["size"]
|
||||
>
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ComponentProps<typeof ShadcnButton> {
|
||||
variant?: ButtonVariant
|
||||
size?: ButtonSize
|
||||
}
|
||||
|
||||
function Button({ variant = "default", size = "default", ...props }: ButtonProps) {
|
||||
return <ShadcnButton variant={variant} size={size} {...props} />
|
||||
}
|
||||
|
||||
export { Button }
|
||||
33
components/elements/Card.config.tsx
Normal file
33
components/elements/Card.config.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ElementConfig } from "./types"
|
||||
|
||||
export const CardConfig: ElementConfig = {
|
||||
Card: {
|
||||
label: "Card",
|
||||
fields: {
|
||||
image: {
|
||||
type: "text",
|
||||
label: "Image URL",
|
||||
},
|
||||
title: {
|
||||
type: "text",
|
||||
label: "Title",
|
||||
},
|
||||
subtitle: {
|
||||
type: "text",
|
||||
label: "Subtitle",
|
||||
},
|
||||
tags: {
|
||||
type: "array",
|
||||
label: "Tags",
|
||||
arrayFields: {
|
||||
tag: {
|
||||
type: "text",
|
||||
label: "Tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default CardConfig
|
||||
73
components/elements/Card.tsx
Normal file
73
components/elements/Card.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as React from "react"
|
||||
|
||||
import {
|
||||
Card as ShadcnCard,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
} from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Image } from "./Image"
|
||||
import { Typography } from "./Typography"
|
||||
|
||||
export interface CardTag {
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface CardProps extends React.ComponentProps<typeof ShadcnCard> {
|
||||
image?: string
|
||||
title?: string
|
||||
subtitle?: string
|
||||
tags?: CardTag[]
|
||||
}
|
||||
|
||||
function Card({
|
||||
image,
|
||||
title,
|
||||
subtitle,
|
||||
tags,
|
||||
className,
|
||||
...props
|
||||
}: CardProps) {
|
||||
return (
|
||||
<ShadcnCard
|
||||
data-slot="element-card"
|
||||
className={cn("overflow-hidden pt-0", className)}
|
||||
{...props}
|
||||
>
|
||||
{image ? (
|
||||
<Image
|
||||
src={image}
|
||||
alt={title ?? ""}
|
||||
objectFit="cover"
|
||||
width={600}
|
||||
height={300}
|
||||
className="h-48 w-full rounded-none"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<CardHeader>
|
||||
{title ? <Typography variant="h5" text={title} /> : null}
|
||||
{subtitle ? (
|
||||
<Typography
|
||||
variant="body2"
|
||||
text={subtitle}
|
||||
className="text-muted-foreground"
|
||||
/>
|
||||
) : null}
|
||||
</CardHeader>
|
||||
|
||||
{tags && tags.length > 0 ? (
|
||||
<CardContent className="flex flex-wrap gap-2">
|
||||
{tags.map((item, index) => (
|
||||
<Badge key={`${item.tag}-${index}`} variant="secondary">
|
||||
{item.tag}
|
||||
</Badge>
|
||||
))}
|
||||
</CardContent>
|
||||
) : null}
|
||||
</ShadcnCard>
|
||||
)
|
||||
}
|
||||
|
||||
export { Card }
|
||||
23
components/elements/Icon.config.tsx
Normal file
23
components/elements/Icon.config.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { ElementConfig } from "./types"
|
||||
|
||||
export const IconConfig: ElementConfig = {
|
||||
Icon: {
|
||||
label: "Icon",
|
||||
fields: {
|
||||
name: {
|
||||
type: "icon",
|
||||
label: "Icon",
|
||||
},
|
||||
size: {
|
||||
type: "text",
|
||||
label: "Size",
|
||||
},
|
||||
className: {
|
||||
type: "text",
|
||||
label: "Class Name",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default IconConfig
|
||||
25
components/elements/Icon.tsx
Normal file
25
components/elements/Icon.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
import { DynamicIcon, type IconName } from "lucide-react/dynamic"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface IconProps
|
||||
extends Omit<React.ComponentProps<typeof DynamicIcon>, "name"> {
|
||||
name: IconName
|
||||
size?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Icon({ name, size = 24, className, ...props }: IconProps) {
|
||||
return (
|
||||
<DynamicIcon
|
||||
name={name}
|
||||
size={size}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Icon }
|
||||
export type { IconName }
|
||||
42
components/elements/Image.config.tsx
Normal file
42
components/elements/Image.config.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ElementConfig } from "./types"
|
||||
|
||||
export const ImageConfig: ElementConfig = {
|
||||
Image: {
|
||||
label: "Image",
|
||||
fields: {
|
||||
src: {
|
||||
type: "text",
|
||||
label: "Source URL",
|
||||
},
|
||||
alt: {
|
||||
type: "text",
|
||||
label: "Alt Text",
|
||||
},
|
||||
objectFit: {
|
||||
type: "select",
|
||||
label: "Object Fit",
|
||||
options: [
|
||||
{ label: "Cover", value: "cover" },
|
||||
{ label: "Contain", value: "contain" },
|
||||
{ label: "Fill", value: "fill" },
|
||||
{ label: "None", value: "none" },
|
||||
{ label: "Scale Down", value: "scale-down" },
|
||||
],
|
||||
},
|
||||
circle: {
|
||||
type: "boolean",
|
||||
label: "Circle",
|
||||
},
|
||||
width: {
|
||||
type: "text",
|
||||
label: "Width",
|
||||
},
|
||||
height: {
|
||||
type: "text",
|
||||
label: "Height",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default ImageConfig
|
||||
56
components/elements/Image.tsx
Normal file
56
components/elements/Image.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as React from "react"
|
||||
import NextImage from "next/image"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export type ObjectFit =
|
||||
| "contain"
|
||||
| "cover"
|
||||
| "fill"
|
||||
| "none"
|
||||
| "scale-down"
|
||||
|
||||
const objectFitStyles: Record<ObjectFit, string> = {
|
||||
contain: "object-contain",
|
||||
cover: "object-cover",
|
||||
fill: "object-fill",
|
||||
none: "object-none",
|
||||
"scale-down": "object-scale-down",
|
||||
}
|
||||
|
||||
export interface ImageProps {
|
||||
src: string
|
||||
alt?: string
|
||||
objectFit?: ObjectFit
|
||||
circle?: boolean
|
||||
height?: number
|
||||
width?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Image({
|
||||
src,
|
||||
alt = "",
|
||||
objectFit = "cover",
|
||||
circle = false,
|
||||
height = 300,
|
||||
width = 300,
|
||||
className,
|
||||
}: ImageProps) {
|
||||
return (
|
||||
<NextImage
|
||||
data-slot="image"
|
||||
src={src}
|
||||
alt={alt}
|
||||
height={height}
|
||||
width={width}
|
||||
className={cn(
|
||||
objectFitStyles[objectFit],
|
||||
circle ? "rounded-full" : "rounded-md",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Image }
|
||||
59
components/elements/Typography.config.tsx
Normal file
59
components/elements/Typography.config.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ElementConfig } from "./types"
|
||||
|
||||
export const TypographyConfig: ElementConfig = {
|
||||
Typography: {
|
||||
label: "Typography",
|
||||
fields: {
|
||||
text: {
|
||||
type: "textarea",
|
||||
label: "Text",
|
||||
},
|
||||
variant: {
|
||||
type: "select",
|
||||
label: "Variant",
|
||||
options: [
|
||||
{ label: "Heading 1", value: "h1" },
|
||||
{ label: "Heading 2", value: "h2" },
|
||||
{ label: "Heading 3", value: "h3" },
|
||||
{ label: "Heading 4", value: "h4" },
|
||||
{ label: "Heading 5", value: "h5" },
|
||||
{ label: "Heading 6", value: "h6" },
|
||||
{ label: "Body 1", value: "body1" },
|
||||
{ label: "Body 2", value: "body2" },
|
||||
{ label: "Caption", value: "caption" },
|
||||
],
|
||||
},
|
||||
textAlign: {
|
||||
type: "select",
|
||||
label: "Text Align",
|
||||
options: [
|
||||
{ label: "Left", value: "left" },
|
||||
{ label: "Center", value: "center" },
|
||||
{ label: "Right", value: "right" },
|
||||
{ label: "Justify", value: "justify" },
|
||||
],
|
||||
},
|
||||
fontWeight: {
|
||||
type: "select",
|
||||
label: "Font Weight",
|
||||
options: [
|
||||
{ label: "Thin", value: "thin" },
|
||||
{ label: "Extra Light", value: "extralight" },
|
||||
{ label: "Light", value: "light" },
|
||||
{ label: "Normal", value: "normal" },
|
||||
{ label: "Medium", value: "medium" },
|
||||
{ label: "Semibold", value: "semibold" },
|
||||
{ label: "Bold", value: "bold" },
|
||||
{ label: "Extra Bold", value: "extrabold" },
|
||||
{ label: "Black", value: "black" },
|
||||
],
|
||||
},
|
||||
className: {
|
||||
type: "text",
|
||||
label: "Class Name",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default TypographyConfig
|
||||
120
components/elements/Typography.tsx
Normal file
120
components/elements/Typography.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export type TypographyVariant =
|
||||
| "h1"
|
||||
| "h2"
|
||||
| "h3"
|
||||
| "h4"
|
||||
| "h5"
|
||||
| "h6"
|
||||
| "body1"
|
||||
| "body2"
|
||||
| "caption"
|
||||
|
||||
export type TypographyAlign = "left" | "center" | "right" | "justify"
|
||||
|
||||
export type TypographyWeight =
|
||||
| "thin"
|
||||
| "extralight"
|
||||
| "light"
|
||||
| "normal"
|
||||
| "medium"
|
||||
| "semibold"
|
||||
| "bold"
|
||||
| "extrabold"
|
||||
| "black"
|
||||
|
||||
const variantStyles: Record<TypographyVariant, string> = {
|
||||
h1: "font-heading scroll-m-20 text-4xl tracking-tight text-balance lg:text-5xl",
|
||||
h2: "font-heading scroll-m-20 text-3xl tracking-tight",
|
||||
h3: "font-heading scroll-m-20 text-2xl tracking-tight",
|
||||
h4: "font-heading scroll-m-20 text-xl tracking-tight",
|
||||
h5: "font-heading scroll-m-20 text-lg tracking-tight",
|
||||
h6: "font-heading scroll-m-20 text-base tracking-tight",
|
||||
body1: "leading-7 text-base",
|
||||
body2: "leading-6 text-sm",
|
||||
caption: "text-xs text-muted-foreground",
|
||||
}
|
||||
|
||||
const variantElement: Record<TypographyVariant, React.ElementType> = {
|
||||
h1: "h1",
|
||||
h2: "h2",
|
||||
h3: "h3",
|
||||
h4: "h4",
|
||||
h5: "h5",
|
||||
h6: "h6",
|
||||
body1: "p",
|
||||
body2: "p",
|
||||
caption: "span",
|
||||
}
|
||||
|
||||
const weightStyles: Record<TypographyWeight, string> = {
|
||||
thin: "font-thin",
|
||||
extralight: "font-extralight",
|
||||
light: "font-light",
|
||||
normal: "font-normal",
|
||||
medium: "font-medium",
|
||||
semibold: "font-semibold",
|
||||
bold: "font-bold",
|
||||
extrabold: "font-extrabold",
|
||||
black: "font-black",
|
||||
}
|
||||
|
||||
const alignStyles: Record<TypographyAlign, string> = {
|
||||
left: "text-left",
|
||||
center: "text-center",
|
||||
right: "text-right",
|
||||
justify: "text-justify",
|
||||
}
|
||||
|
||||
const defaultWeight: Record<TypographyVariant, TypographyWeight> = {
|
||||
h1: "bold",
|
||||
h2: "bold",
|
||||
h3: "bold",
|
||||
h4: "bold",
|
||||
h5: "bold",
|
||||
h6: "bold",
|
||||
body1: "normal",
|
||||
body2: "normal",
|
||||
caption: "normal",
|
||||
}
|
||||
|
||||
export interface TypographyProps
|
||||
extends Omit<React.HTMLAttributes<HTMLElement>, "children"> {
|
||||
text?: React.ReactNode
|
||||
variant?: TypographyVariant
|
||||
textAlign?: TypographyAlign
|
||||
fontWeight?: TypographyWeight
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Typography({
|
||||
text,
|
||||
variant = "body1",
|
||||
textAlign,
|
||||
fontWeight,
|
||||
className,
|
||||
...props
|
||||
}: TypographyProps) {
|
||||
const Comp = variantElement[variant]
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="typography"
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
variantStyles[variant],
|
||||
weightStyles[fontWeight ?? defaultWeight[variant]],
|
||||
textAlign && alignStyles[textAlign],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{text}
|
||||
</Comp>
|
||||
)
|
||||
}
|
||||
|
||||
export { Typography }
|
||||
26
components/elements/index.ts
Normal file
26
components/elements/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export * from "./types"
|
||||
|
||||
export { Typography } from "./Typography"
|
||||
export type {
|
||||
TypographyProps,
|
||||
TypographyVariant,
|
||||
TypographyAlign,
|
||||
TypographyWeight,
|
||||
} from "./Typography"
|
||||
export { TypographyConfig } from "./Typography.config"
|
||||
|
||||
export { Button } from "./Button"
|
||||
export type { ButtonProps, ButtonVariant, ButtonSize } from "./Button"
|
||||
export { ButtonConfig } from "./Button.config"
|
||||
|
||||
export { Image } from "./Image"
|
||||
export type { ImageProps, ObjectFit } from "./Image"
|
||||
export { ImageConfig } from "./Image.config"
|
||||
|
||||
export { Icon } from "./Icon"
|
||||
export type { IconProps, IconName } from "./Icon"
|
||||
export { IconConfig } from "./Icon.config"
|
||||
|
||||
export { Card } from "./Card"
|
||||
export type { CardProps, CardTag } from "./Card"
|
||||
export { CardConfig } from "./Card.config"
|
||||
29
components/elements/types.ts
Normal file
29
components/elements/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type FieldType =
|
||||
| "text"
|
||||
| "textarea"
|
||||
| "color"
|
||||
| "icon"
|
||||
| "array"
|
||||
| "object"
|
||||
| "boolean"
|
||||
| "select"
|
||||
|
||||
export interface FieldOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface FieldConfig {
|
||||
type: FieldType
|
||||
label?: string
|
||||
options?: FieldOption[]
|
||||
arrayFields?: Record<string, FieldConfig>
|
||||
objectFields?: Record<string, FieldConfig>
|
||||
}
|
||||
|
||||
export interface ComponentConfig {
|
||||
label: string
|
||||
fields: Record<string, FieldConfig>
|
||||
}
|
||||
|
||||
export type ElementConfig = Record<string, ComponentConfig>
|
||||
Reference in New Issue
Block a user