diff --git a/Htmx.ApiDemo/Properties/launchSettings.json b/Htmx.ApiDemo/Properties/launchSettings.json index e74f83a..12cf25f 100644 --- a/Htmx.ApiDemo/Properties/launchSettings.json +++ b/Htmx.ApiDemo/Properties/launchSettings.json @@ -4,8 +4,8 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "todos", + "launchBrowser": false, + "launchUrl": "/", "applicationUrl": "http://localhost:5120", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Htmx.ApiDemo/Templates/Components/Accordion.htmx b/Htmx.ApiDemo/Templates/Components/Accordion.htmx new file mode 100644 index 0000000..79733d7 --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Accordion.htmx @@ -0,0 +1,3 @@ +
{description}
"""); + sb.Append("{description}
"""); + sb.Append("{description}
""".ToUtf8Bytes(); + } + + protected override void RenderId(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_idData); + protected override void RenderName(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_nameData); + protected override void RenderAccept(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_acceptData); + protected override void RenderMultiple(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_multipleData); + protected override void RenderLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_labelData); + protected override void RenderDescription(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_descriptionData); + protected override void RenderExtraClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_extraClassesData); + protected override void RenderHxAttrs(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_hxAttrsData); +} diff --git a/Htmx.ApiDemo/Templates/Components/Pagination.htmx b/Htmx.ApiDemo/Templates/Components/Pagination.htmx new file mode 100644 index 0000000..490ce26 --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Pagination.htmx @@ -0,0 +1,5 @@ + diff --git a/Htmx.ApiDemo/Templates/Components/Pagination.htmx.cs b/Htmx.ApiDemo/Templates/Components/Pagination.htmx.cs new file mode 100644 index 0000000..71d73a2 --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Pagination.htmx.cs @@ -0,0 +1,49 @@ +namespace Htmx.ApiDemo.Templates.Components; + +///{description}
""".ToUtf8Bytes(); + } + + protected override void RenderId(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_idData); + protected override void RenderName(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_nameData); + protected override void RenderMin(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_minData); + protected override void RenderMax(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_maxData); + protected override void RenderStep(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_stepData); + protected override void RenderValue(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_valueData); + protected override void RenderLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_labelData); + protected override void RenderDescription(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_descriptionData); + protected override void RenderExtraClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_extraClassesData); + protected override void RenderHxAttrs(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_hxAttrsData); +} diff --git a/Htmx.ApiDemo/Templates/Components/Switch.htmx b/Htmx.ApiDemo/Templates/Components/Switch.htmx new file mode 100644 index 0000000..f1ad1cc --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Switch.htmx @@ -0,0 +1,10 @@ + diff --git a/Htmx.ApiDemo/Templates/Components/Switch.htmx.cs b/Htmx.ApiDemo/Templates/Components/Switch.htmx.cs new file mode 100644 index 0000000..57963c0 --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Switch.htmx.cs @@ -0,0 +1,32 @@ +namespace Htmx.ApiDemo.Templates.Components; + +///{description}
""".ToUtf8Bytes(); + } + + protected override void RenderId(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_idData); + protected override void RenderName(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_nameData); + protected override void RenderRows(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_rowsData); + protected override void RenderPlaceholder(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_placeholderData); + protected override void RenderLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_labelData); + protected override void RenderDescription(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_descriptionData); + protected override void RenderDefaultValue(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_defaultValueData); + protected override void RenderExtraClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_extraClassesData); + protected override void RenderHxAttrs(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_hxAttrsData); +} diff --git a/Htmx.ApiDemo/Templates/Components/Toast.htmx b/Htmx.ApiDemo/Templates/Components/Toast.htmx new file mode 100644 index 0000000..d6d9665 --- /dev/null +++ b/Htmx.ApiDemo/Templates/Components/Toast.htmx @@ -0,0 +1,18 @@ +Horizontal
+ $$SeparatorH$$ +Vertical (inline)
+Cards group related content and provide a contained, elevated surface for information.
", + footer: ""); + + // Separator + SeparatorH = new Separator(); + SeparatorV = new Separator(orientation: "vertical"); + + // Skeleton + SkeletonTitle = new Skeleton("h-5 w-48"); + SkeletonLine1 = new Skeleton("h-4 w-full"); + SkeletonLine2 = new Skeleton("h-4 w-3/4"); + SkeletonAvatar = new Skeleton("h-10 w-10 rounded-full"); + + // Avatar + AvatarSm = new Avatar("SM", size: "sm"); + AvatarDefault = new Avatar("JD"); + AvatarLg = new Avatar("AB", size: "lg"); + AvatarImg = new Avatar("GitHub", src: "https://github.com/github.png", size: "default"); + + // Progress + Progress25 = new Progress(25); + Progress60 = new Progress(60); + Progress100 = new Progress(100); + + // Alert + AlertDefault = new Alert("Information", description: "This is an informational alert with a default style."); + AlertDestructive = new Alert("Error", description: "Something went wrong. Please check your input.", variant: "destructive"); + + // Breadcrumb + BreadcrumbDemo = new Breadcrumb([ + ("Home", "/"), + ("Components", "/components"), + ("UI Demo", ""), + ]); + + // Checkbox + CheckboxAccept = new Checkbox("accept-terms", label: "Accept terms and conditions"); + CheckboxChecked = new Checkbox("newsletter", label: "Subscribe to newsletter", @checked: true); + + // RadioGroup + RadioGroupCol = new RadioGroup( + name: "plan-v", + label: "Plan (vertical)", + options: [ + ("starter", "Starter", true), + ("pro", "Pro", false), + ("enterprise", "Enterprise", false), + ]); + RadioGroupRow = new RadioGroup( + name: "size-h", + label: "Size (horizontal)", + direction: "flex-row", + options: [ + ("sm", "SM", true), + ("md", "MD", false), + ("lg", "LG", false), + ]); + + // Switch + SwitchOff = new Switch("notif-off", label: "Notifications"); + SwitchOn = new Switch("darkmode", label: "Dark mode", isChecked: true); + + // Textarea + TextareaDemo = new Textarea( + id: "bio", + label: "Bio", + placeholder: "Tell us about yourself…", + description: "Max 200 characters."); + + // Slider + SliderDemo = new Slider(id: "volume", label: "Volume", value: 40, description: "Drag to adjust"); + + // FileInput + FileInputDemo = new FileInput( + id: "avatar-upload", + label: "Profile picture", + accept: "image/*", + description: "PNG, JPG or GIF up to 2 MB."); + + // Table + TableDemo = new Table( + headers: ["Name", "Role", "Status"], + rows: [ + ["Alice", "Admin", "Active"], + ["Bob", "Editor", "Active"], + ["Charlie", "Viewer", "Inactive"], + ], + caption: "Team members"); + + // Pagination + PaginationDemo = new Pagination(current: 3, total: 7, urlPattern: "/ui-demo?page={0}"); + + // Tabs + TabsDemo = new Tabs("demo", [ + ("overview", "Overview", "This is the overview tab content.
"), + ("settings", "Settings", "Manage your settings here.
"), + ("billing", "Billing", "View billing information.
"), + ]); + + // Accordion + AccordionDemo = new Accordion("demo-acc", [ + ("What is htmx?", "htmx allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML."), + ("Is it production ready?", "Yes — htmx is used by thousands of projects in production worldwide."), + ("Does it replace React?", "htmx is a different tool suited to server-driven UIs. Use whatever fits your team best."), + ], openIndex: 0); + + // Tooltip + TooltipTop = new Tooltip("Top tooltip", new Button("Hover me"), position: "top"); + TooltipBottom = new Tooltip("Bottom tooltip", new Button("Bottom", variant: "secondary"), position: "bottom"); + TooltipRight = new Tooltip("Right tooltip", new Button("Right", variant: "outline"), position: "right"); + + // Dialog + DialogDemo = new Dialog( + id: "demo-dialog", + title: "Are you sure?", + description: "This action cannot be undone.", + content: "Please confirm that you want to proceed with this operation.", + footer: "" + + ""); + + // Dropdown + DropdownDemo = new DropdownMenu( + trigger: new Button("Options ▾", variant: "outline"), + items: [ + ("Edit", "/edit", false), + ("Duplicate", "/dup", false), + ("", "", true), // separator + ("Delete", "/delete", false), + ]); + + // Toast Viewport + ToastViewportDemo = new ToastViewport(); } protected override void RenderBtnDefault(HtmxRenderContext ctx) => BtnDefault.Render(ctx); @@ -71,11 +303,66 @@ public sealed class UiDemo : UiDemoBase protected override void RenderInputPassword(HtmxRenderContext ctx) => InputPassword.Render(ctx); protected override void RenderInputSearch(HtmxRenderContext ctx) => InputSearch.Render(ctx); - protected override void RenderSelectDemo(HtmxRenderContext ctx) => SelectDemo.Render(ctx); - protected override void RenderCalendarDemo(HtmxRenderContext ctx) => CalendarDemo.Render(ctx); - protected override void RenderCalendarRangeDemo(HtmxRenderContext ctx) => CalendarRangeDemo.Render(ctx); - protected override void RenderTimePickerDemo(HtmxRenderContext ctx) => TimePickerDemo.Render(ctx); - protected override void RenderTimePicker12hDemo(HtmxRenderContext ctx) => TimePicker12hDemo.Render(ctx); + protected override void RenderSelectDemo(HtmxRenderContext ctx) => SelectDemo.Render(ctx); + protected override void RenderCalendarDemo(HtmxRenderContext ctx) => CalendarDemo.Render(ctx); + protected override void RenderCalendarRangeDemo(HtmxRenderContext ctx) => CalendarRangeDemo.Render(ctx); + protected override void RenderTimePickerDemo(HtmxRenderContext ctx) => TimePickerDemo.Render(ctx); + protected override void RenderTimePicker12hDemo(HtmxRenderContext ctx) => TimePicker12hDemo.Render(ctx); + + protected override void RenderBadgeDefault(HtmxRenderContext ctx) => BadgeDefault.Render(ctx); + protected override void RenderBadgeSecondary(HtmxRenderContext ctx) => BadgeSecondary.Render(ctx); + protected override void RenderBadgeDestructive(HtmxRenderContext ctx) => BadgeDestructive.Render(ctx); + protected override void RenderBadgeOutline(HtmxRenderContext ctx) => BadgeOutline.Render(ctx); + + protected override void RenderCardDemo(HtmxRenderContext ctx) => CardDemo.Render(ctx); + + protected override void RenderSeparatorH(HtmxRenderContext ctx) => SeparatorH.Render(ctx); + protected override void RenderSeparatorV(HtmxRenderContext ctx) => SeparatorV.Render(ctx); + + protected override void RenderSkeletonTitle(HtmxRenderContext ctx) => SkeletonTitle.Render(ctx); + protected override void RenderSkeletonLine1(HtmxRenderContext ctx) => SkeletonLine1.Render(ctx); + protected override void RenderSkeletonLine2(HtmxRenderContext ctx) => SkeletonLine2.Render(ctx); + protected override void RenderSkeletonAvatar(HtmxRenderContext ctx) => SkeletonAvatar.Render(ctx); + + protected override void RenderAvatarSm(HtmxRenderContext ctx) => AvatarSm.Render(ctx); + protected override void RenderAvatarDefault(HtmxRenderContext ctx) => AvatarDefault.Render(ctx); + protected override void RenderAvatarLg(HtmxRenderContext ctx) => AvatarLg.Render(ctx); + protected override void RenderAvatarImg(HtmxRenderContext ctx) => AvatarImg.Render(ctx); + + protected override void RenderProgress25(HtmxRenderContext ctx) => Progress25.Render(ctx); + protected override void RenderProgress60(HtmxRenderContext ctx) => Progress60.Render(ctx); + protected override void RenderProgress100(HtmxRenderContext ctx) => Progress100.Render(ctx); + + protected override void RenderAlertDefault(HtmxRenderContext ctx) => AlertDefault.Render(ctx); + protected override void RenderAlertDestructive(HtmxRenderContext ctx) => AlertDestructive.Render(ctx); + + protected override void RenderBreadcrumbDemo(HtmxRenderContext ctx) => BreadcrumbDemo.Render(ctx); + + protected override void RenderCheckboxAccept(HtmxRenderContext ctx) => CheckboxAccept.Render(ctx); + protected override void RenderCheckboxChecked(HtmxRenderContext ctx) => CheckboxChecked.Render(ctx); + + protected override void RenderRadioGroupCol(HtmxRenderContext ctx) => RadioGroupCol.Render(ctx); + protected override void RenderRadioGroupRow(HtmxRenderContext ctx) => RadioGroupRow.Render(ctx); + + protected override void RenderSwitchOff(HtmxRenderContext ctx) => SwitchOff.Render(ctx); + protected override void RenderSwitchOn(HtmxRenderContext ctx) => SwitchOn.Render(ctx); + + protected override void RenderTextareaDemo(HtmxRenderContext ctx) => TextareaDemo.Render(ctx); + protected override void RenderSliderDemo(HtmxRenderContext ctx) => SliderDemo.Render(ctx); + protected override void RenderFileInputDemo(HtmxRenderContext ctx)=> FileInputDemo.Render(ctx); + protected override void RenderTableDemo(HtmxRenderContext ctx) => TableDemo.Render(ctx); + protected override void RenderPaginationDemo(HtmxRenderContext ctx)=> PaginationDemo.Render(ctx); + protected override void RenderTabsDemo(HtmxRenderContext ctx) => TabsDemo.Render(ctx); + protected override void RenderAccordionDemo(HtmxRenderContext ctx)=> AccordionDemo.Render(ctx); + + protected override void RenderTooltipTop(HtmxRenderContext ctx) => TooltipTop.Render(ctx); + protected override void RenderTooltipBottom(HtmxRenderContext ctx) => TooltipBottom.Render(ctx); + protected override void RenderTooltipRight(HtmxRenderContext ctx) => TooltipRight.Render(ctx); + + protected override void RenderDialogDemo(HtmxRenderContext ctx) => DialogDemo.Render(ctx); + protected override void RenderDropdownDemo(HtmxRenderContext ctx) => DropdownDemo.Render(ctx); + + protected override void RenderToastViewportDemo(HtmxRenderContext ctx) => ToastViewportDemo.Render(ctx); } diff --git a/Htmx.ApiDemo/wwwroot/css/input.css b/Htmx.ApiDemo/wwwroot/css/input.css index 3d520cd..518e0aa 100644 --- a/Htmx.ApiDemo/wwwroot/css/input.css +++ b/Htmx.ApiDemo/wwwroot/css/input.css @@ -1,5 +1,8 @@ @import "tailwindcss"; +@source "../../**/*.{html,htmx,cs}"; +@source "../../src/**/!(*.g).cs"; + @theme { --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); diff --git a/Htmx.ApiDemo/wwwroot/css/output.css b/Htmx.ApiDemo/wwwroot/css/output.css index c5a2acc..e9351c3 100644 --- a/Htmx.ApiDemo/wwwroot/css/output.css +++ b/Htmx.ApiDemo/wwwroot/css/output.css @@ -8,9 +8,12 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-black: #000; + --color-white: #fff; --spacing: 0.25rem; --container-xs: 20rem; --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; --container-xl: 36rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); @@ -22,12 +25,18 @@ --text-lg--line-height: calc(1.75 / 1.125); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); + --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --tracking-tight: -0.025em; + --leading-relaxed: 1.625; + --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --blur-sm: 8px; --default-transition-duration: 150ms; --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); @@ -35,10 +44,14 @@ --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + --color-popover: hsl(var(--popover)); + --color-popover-foreground: hsl(var(--popover-foreground)); --color-primary: hsl(var(--primary)); --color-primary-foreground: hsl(var(--primary-foreground)); --color-secondary: hsl(var(--secondary)); --color-secondary-foreground: hsl(var(--secondary-foreground)); + --color-muted: hsl(var(--muted)); --color-muted-foreground: hsl(var(--muted-foreground)); --color-accent: hsl(var(--accent)); --color-accent-foreground: hsl(var(--accent-foreground)); @@ -198,12 +211,32 @@ } } @layer utilities { + .pointer-events-auto { + pointer-events: auto; + } .pointer-events-none { pointer-events: none; } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + } + .absolute { + position: absolute; + } .fixed { position: fixed; } + .relative { + position: relative; + } .static { position: static; } @@ -228,30 +261,108 @@ .end { inset-inline-end: var(--spacing); } + .top-1\/2 { + top: calc(1 / 2 * 100%); + } + .top-full { + top: 100%; + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .right-4 { + right: calc(var(--spacing) * 4); + } + .right-full { + right: 100%; + } + .bottom-4 { + bottom: calc(var(--spacing) * 4); + } + .bottom-full { + bottom: 100%; + } .left-0 { left: calc(var(--spacing) * 0); } + .left-1\/2 { + left: calc(1 / 2 * 100%); + } + .left-full { + left: 100%; + } .z-20 { z-index: 20; } .z-30 { z-index: 30; } + .z-50 { + z-index: 50; + } + .z-\[100\] { + z-index: 100; + } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .m-0 { + margin: calc(var(--spacing) * 0); + } + .-mx-1 { + margin-inline: calc(var(--spacing) * -1); + } + .my-1 { + margin-block: calc(var(--spacing) * 1); + } .mt-1 { margin-top: calc(var(--spacing) * 1); } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } .mt-3 { margin-top: calc(var(--spacing) * 3); } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mr-2 { + margin-right: calc(var(--spacing) * 2); + } .mb-1 { margin-bottom: calc(var(--spacing) * 1); } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } .mb-3 { margin-bottom: calc(var(--spacing) * 3); } .mb-4 { margin-bottom: calc(var(--spacing) * 4); } + .ml-2 { + margin-left: calc(var(--spacing) * 2); + } + .block { + display: block; + } .flex { display: flex; } @@ -270,12 +381,24 @@ .inline-flex { display: inline-flex; } + .aspect-square { + aspect-ratio: 1 / 1; + } + .h-2 { + height: calc(var(--spacing) * 2); + } + .h-3\.5 { + height: calc(var(--spacing) * 3.5); + } .h-4 { height: calc(var(--spacing) * 4); } .h-5 { height: calc(var(--spacing) * 5); } + .h-6 { + height: calc(var(--spacing) * 6); + } .h-8 { height: calc(var(--spacing) * 8); } @@ -288,18 +411,45 @@ .h-11 { height: calc(var(--spacing) * 11); } + .h-12 { + height: calc(var(--spacing) * 12); + } + .h-14 { + height: calc(var(--spacing) * 14); + } .h-16 { height: calc(var(--spacing) * 16); } + .h-20 { + height: calc(var(--spacing) * 20); + } + .h-full { + height: 100%; + } + .h-px { + height: 1px; + } + .max-h-screen { + max-height: 100vh; + } .min-h-4 { min-height: calc(var(--spacing) * 4); } + .min-h-20 { + min-height: calc(var(--spacing) * 20); + } .min-h-dvh { min-height: 100dvh; } .min-h-full { min-height: 100%; } + .w-3\.5 { + width: calc(var(--spacing) * 3.5); + } + .w-3\/4 { + width: calc(3 / 4 * 100%); + } .w-4 { width: calc(var(--spacing) * 4); } @@ -315,15 +465,39 @@ .w-10 { width: calc(var(--spacing) * 10); } + .w-11 { + width: calc(var(--spacing) * 11); + } + .w-14 { + width: calc(var(--spacing) * 14); + } .w-16 { width: calc(var(--spacing) * 16); } + .w-20 { + width: calc(var(--spacing) * 20); + } + .w-48 { + width: calc(var(--spacing) * 48); + } .w-64 { width: calc(var(--spacing) * 64); } .w-full { width: 100%; } + .w-max { + width: max-content; + } + .w-px { + width: 1px; + } + .max-w-lg { + max-width: var(--container-lg); + } + .max-w-md { + max-width: var(--container-md); + } .max-w-sm { max-width: var(--container-sm); } @@ -333,6 +507,9 @@ .max-w-xs { max-width: var(--container-xs); } + .min-w-40 { + min-width: calc(var(--spacing) * 40); + } .min-w-72 { min-width: calc(var(--spacing) * 72); } @@ -342,13 +519,41 @@ .shrink-0 { flex-shrink: 0; } + .caption-bottom { + caption-side: bottom; + } + .-translate-x-1\/2 { + --tw-translate-x: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } .-translate-x-full { --tw-translate-x: -100%; translate: var(--tw-translate-x) var(--tw-translate-y); } + .translate-x-0\.5 { + --tw-translate-x: calc(var(--spacing) * 0.5); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .-translate-y-1\/2 { + --tw-translate-y: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-y-2 { + --tw-translate-y: calc(var(--spacing) * 2); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .animate-pulse { + animation: var(--animate-pulse); + } .cursor-pointer { cursor: pointer; } + .resize-y { + resize: vertical; + } .appearance-none { appearance: none; } @@ -361,18 +566,33 @@ .flex-col { flex-direction: column; } + .flex-col-reverse { + flex-direction: column-reverse; + } + .flex-row { + flex-direction: row; + } .flex-wrap { flex-wrap: wrap; } .items-center { align-items: center; } + .items-start { + align-items: flex-start; + } .justify-between { justify-content: space-between; } .justify-center { justify-content: center; } + .justify-end { + justify-content: flex-end; + } + .justify-start { + justify-content: flex-start; + } .gap-0\.5 { gap: calc(var(--spacing) * 0.5); } @@ -391,6 +611,9 @@ .gap-4 { gap: calc(var(--spacing) * 4); } + .gap-6 { + gap: calc(var(--spacing) * 6); + } .gap-8 { gap: calc(var(--spacing) * 8); } @@ -401,6 +624,13 @@ margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); } } + .space-y-1\.5 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse))); + } + } .space-y-2 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -429,6 +659,30 @@ margin-block-end: calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse))); } } + .space-x-4 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); + } + } + .divide-y { + :where(& > :not(:last-child)) { + --tw-divide-y-reverse: 0; + border-bottom-style: var(--tw-border-style); + border-top-style: var(--tw-border-style); + border-top-width: calc(1px * var(--tw-divide-y-reverse)); + border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + } + } + .divide-border { + :where(& > :not(:last-child)) { + border-color: var(--color-border); + } + } + .overflow-auto { + overflow: auto; + } .overflow-hidden { overflow: hidden; } @@ -438,9 +692,15 @@ .rounded-full { border-radius: calc(infinity * 1px); } + .rounded-lg { + border-radius: var(--radius-lg); + } .rounded-md { border-radius: var(--radius-md); } + .rounded-sm { + border-radius: var(--radius-sm); + } .border { border-style: var(--tw-border-style); border-width: 1px; @@ -460,12 +720,21 @@ .border-border { border-color: var(--color-border); } + .border-destructive { + border-color: var(--color-destructive); + } .border-destructive\/30 { border-color: color-mix(in srgb, hsl(var(--destructive)) 30%, transparent); @supports (color: color-mix(in lab, red, red)) { border-color: color-mix(in oklab, var(--color-destructive) 30%, transparent); } } + .border-destructive\/50 { + border-color: color-mix(in srgb, hsl(var(--destructive)) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-destructive) 50%, transparent); + } + } .border-input { border-color: var(--color-input); } @@ -478,6 +747,9 @@ background-color: color-mix(in oklab, var(--color-black) 50%, transparent); } } + .bg-border { + background-color: var(--color-border); + } .bg-card { background-color: var(--color-card); } @@ -496,6 +768,21 @@ background-color: color-mix(in oklab, var(--color-destructive) 15%, transparent); } } + .bg-input { + background-color: var(--color-input); + } + .bg-muted { + background-color: var(--color-muted); + } + .bg-muted\/50 { + background-color: color-mix(in srgb, hsl(var(--muted)) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-muted) 50%, transparent); + } + } + .bg-popover { + background-color: var(--color-popover); + } .bg-primary { background-color: var(--color-primary); } @@ -505,6 +792,18 @@ .bg-transparent { background-color: transparent; } + .bg-white { + background-color: var(--color-white); + } + .object-cover { + object-fit: cover; + } + .p-0 { + padding: calc(var(--spacing) * 0); + } + .p-1 { + padding: calc(var(--spacing) * 1); + } .p-4 { padding: calc(var(--spacing) * 4); } @@ -514,6 +813,9 @@ .px-2 { padding-inline: calc(var(--spacing) * 2); } + .px-2\.5 { + padding-inline: calc(var(--spacing) * 2.5); + } .px-3 { padding-inline: calc(var(--spacing) * 3); } @@ -532,6 +834,9 @@ .py-1 { padding-block: calc(var(--spacing) * 1); } + .py-1\.5 { + padding-block: calc(var(--spacing) * 1.5); + } .py-2 { padding-block: calc(var(--spacing) * 2); } @@ -544,9 +849,24 @@ .py-12 { padding-block: calc(var(--spacing) * 12); } + .pt-0 { + padding-top: calc(var(--spacing) * 0); + } + .pt-4 { + padding-top: calc(var(--spacing) * 4); + } + .pb-4 { + padding-bottom: calc(var(--spacing) * 4); + } .text-center { text-align: center; } + .text-left { + text-align: left; + } + .align-middle { + vertical-align: middle; + } .text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); @@ -579,6 +899,10 @@ --tw-font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium); } + .font-normal { + --tw-font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-normal); + } .font-semibold { --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); @@ -587,9 +911,15 @@ --tw-tracking: var(--tracking-tight); letter-spacing: var(--tracking-tight); } + .break-words { + overflow-wrap: break-word; + } .whitespace-nowrap { white-space: nowrap; } + .text-card-foreground { + color: var(--color-card-foreground); + } .text-destructive { color: var(--color-destructive); } @@ -602,6 +932,9 @@ .text-muted-foreground { color: var(--color-muted-foreground); } + .text-popover-foreground { + color: var(--color-popover-foreground); + } .text-primary { color: var(--color-primary); } @@ -618,9 +951,18 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + .accent-primary { + accent-color: var(--color-primary); + } .opacity-0 { opacity: 0%; } + .opacity-70 { + opacity: 70%; + } + .opacity-90 { + opacity: 90%; + } .shadow { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -629,10 +971,18 @@ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .shadow-sm { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .ring-0 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .ring-offset-background { --tw-ring-offset-color: var(--color-background); } @@ -648,6 +998,11 @@ -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } .transition-colors { transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); @@ -663,6 +1018,14 @@ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); } + .duration-150 { + --tw-duration: 150ms; + transition-duration: 150ms; + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } .duration-300 { --tw-duration: 300ms; transition-duration: 300ms; @@ -671,10 +1034,21 @@ --tw-ease: var(--ease-in-out); transition-timing-function: var(--ease-in-out); } + .outline-none { + --tw-outline-style: none; + outline-style: none; + } .select-none { -webkit-user-select: none; user-select: none; } + .group-hover\:opacity-100 { + &:is(:where(.group):hover *) { + @media (hover: hover) { + opacity: 100%; + } + } + } .peer-disabled\:cursor-not-allowed { &:is(:where(.peer):disabled ~ *) { cursor: not-allowed; @@ -685,11 +1059,59 @@ opacity: 70%; } } + .file\:border-0 { + &::file-selector-button { + border-style: var(--tw-border-style); + border-width: 0px; + } + } + .file\:bg-transparent { + &::file-selector-button { + background-color: transparent; + } + } + .file\:text-sm { + &::file-selector-button { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + } + .file\:font-medium { + &::file-selector-button { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + } + .file\:text-foreground { + &::file-selector-button { + color: var(--color-foreground); + } + } .placeholder\:text-muted-foreground { &::placeholder { color: var(--color-muted-foreground); } } + .backdrop\:bg-black\/50 { + &::backdrop { + background-color: color-mix(in srgb, #000 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-black) 50%, transparent); + } + } + } + .backdrop\:backdrop-blur-sm { + &::backdrop { + --tw-backdrop-blur: blur(var(--blur-sm)); + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + } + } + .open\:block { + &:is([open], :popover-open, :open) { + display: block; + } + } .hover\:bg-accent { &:hover { @media (hover: hover) { @@ -697,6 +1119,16 @@ } } } + .hover\:bg-destructive\/80 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, hsl(var(--destructive)) 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-destructive) 80%, transparent); + } + } + } + } .hover\:bg-destructive\/90 { &:hover { @media (hover: hover) { @@ -707,6 +1139,26 @@ } } } + .hover\:bg-muted\/50 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, hsl(var(--muted)) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-muted) 50%, transparent); + } + } + } + } + .hover\:bg-primary\/80 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in srgb, hsl(var(--primary)) 80%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-primary) 80%, transparent); + } + } + } + } .hover\:bg-primary\/90 { &:hover { @media (hover: hover) { @@ -734,6 +1186,13 @@ } } } + .hover\:text-foreground { + &:hover { + @media (hover: hover) { + color: var(--color-foreground); + } + } + } .hover\:underline { &:hover { @media (hover: hover) { @@ -741,6 +1200,29 @@ } } } + .hover\:opacity-100 { + &:hover { + @media (hover: hover) { + opacity: 100%; + } + } + } + .focus\:bg-accent { + &:focus { + background-color: var(--color-accent); + } + } + .focus\:text-accent-foreground { + &:focus { + color: var(--color-accent-foreground); + } + } + .focus\:ring-1 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } .focus\:ring-2 { &:focus { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); @@ -813,6 +1295,11 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } } + .sm\:flex-col { + @media (width >= 40rem) { + flex-direction: column; + } + } .md\:relative { @media (width >= 48rem) { position: relative; @@ -823,6 +1310,11 @@ display: none; } } + .md\:max-w-\[420px\] { + @media (width >= 48rem) { + max-width: 420px; + } + } .md\:translate-x-0 { @media (width >= 48rem) { --tw-translate-x: calc(var(--spacing) * 0); @@ -840,12 +1332,59 @@ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } } + .\[\&_p\]\:leading-relaxed { + & p { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed); + } + } .\[\&\.open\]\:translate-x-0 { &.open { --tw-translate-x: calc(var(--spacing) * 0); translate: var(--tw-translate-x) var(--tw-translate-y); } } + .\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0 { + &:has([role=checkbox]) { + padding-right: calc(var(--spacing) * 0); + } + } + .\[\&\>svg\]\:absolute { + &>svg { + position: absolute; + } + } + .\[\&\>svg\]\:top-4 { + &>svg { + top: calc(var(--spacing) * 4); + } + } + .\[\&\>svg\]\:left-4 { + &>svg { + left: calc(var(--spacing) * 4); + } + } + .\[\&\>svg\]\:text-destructive { + &>svg { + color: var(--color-destructive); + } + } + .\[\&\>svg\]\:text-foreground { + &>svg { + color: var(--color-foreground); + } + } + .\[\&\>svg\+div\]\:translate-y-\[-3px\] { + &>svg+div { + --tw-translate-y: -3px; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + } + .\[\&\>svg\~\*\]\:pl-7 { + &>svg~* { + padding-left: calc(var(--spacing) * 7); + } + } } @layer base { :root { @@ -1121,11 +1660,41 @@ inherits: false; initial-value: 0; } +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} @property --tw-space-y-reverse { syntax: "*"; inherits: false; initial-value: 0; } +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-divide-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} @property --tw-border-style { syntax: "*"; inherits: false; @@ -1310,13 +1879,25 @@ syntax: "*"; inherits: false; } +@keyframes pulse { + 50% { + opacity: 0.5; + } +} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { --tw-translate-x: 0; --tw-translate-y: 0; --tw-translate-z: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-divide-y-reverse: 0; --tw-border-style: solid; --tw-leading: initial; --tw-font-weight: initial; diff --git a/Htmx.ApiDemo/wwwroot/js/components.js b/Htmx.ApiDemo/wwwroot/js/components.js index 2b360e9..e8db1f0 100644 --- a/Htmx.ApiDemo/wwwroot/js/components.js +++ b/Htmx.ApiDemo/wwwroot/js/components.js @@ -456,3 +456,262 @@ document.addEventListener('DOMContentLoaded', initAll); document.addEventListener('htmx:afterSwap', initAll); })(); + + +// ── Switch ──────────────────────────────────────────────────────────────── + +(function () { + function updateSwitch(input) { + var track = input.parentElement && input.parentElement.querySelector('.switch-track'); + if (!track) return; + var thumb = track.querySelector('.switch-thumb'); + if (input.checked) { + track.classList.add('bg-primary'); + track.classList.remove('bg-input'); + if (thumb) thumb.style.transform = 'translateX(1.375rem)'; + } else { + track.classList.remove('bg-primary'); + track.classList.add('bg-input'); + if (thumb) thumb.style.transform = ''; + } + } + + function initAll() { + document.querySelectorAll('.switch-checkbox').forEach(function (input) { + updateSwitch(input); + if (!input._switchBound) { + input._switchBound = true; + input.addEventListener('change', function () { updateSwitch(input); }); + } + }); + } + + document.addEventListener('DOMContentLoaded', initAll); + document.addEventListener('htmx:afterSwap', initAll); +})(); + + +// ── Tabs ────────────────────────────────────────────────────────────────── + +(function () { + var ACTIVE = 'bg-background text-foreground shadow-sm'; + var INACTIVE = 'text-muted-foreground'; + + function initTabs(root) { + if (root._tabsInitialised) return; + root._tabsInitialised = true; + + var triggers = Array.from(root.querySelectorAll('.tabs-trigger')); + var panels = Array.from(root.querySelectorAll('.tabs-panel')); + + function activate(idx) { + triggers.forEach(function (t, i) { + var active = i === idx; + t.setAttribute('aria-selected', String(active)); + ACTIVE.split(' ').forEach(function (c) { t.classList.toggle(c, active); }); + INACTIVE.split(' ').forEach(function (c) { t.classList.toggle(c, !active); }); + }); + panels.forEach(function (p, i) { p.hidden = i !== idx; }); + } + + triggers.forEach(function (trigger, idx) { + trigger.addEventListener('click', function () { activate(idx); }); + }); + activate(0); + } + + function initAll() { + document.querySelectorAll('.tabs-root').forEach(initTabs); + } + + document.addEventListener('DOMContentLoaded', initAll); + document.addEventListener('htmx:afterSwap', initAll); +})(); + + +// ── Accordion ───────────────────────────────────────────────────────────── + +(function () { + function initAccordion(root) { + if (root._accInitialised) return; + root._accInitialised = true; + + root.querySelectorAll('.accordion-trigger').forEach(function (trigger) { + trigger.addEventListener('click', function () { + var expanded = trigger.getAttribute('aria-expanded') === 'true'; + var panel = trigger.closest('.accordion-item').querySelector('.accordion-panel'); + if (expanded) { + trigger.setAttribute('aria-expanded', 'false'); + panel.style.height = '0'; + panel.style.opacity = '0'; + } else { + trigger.setAttribute('aria-expanded', 'true'); + panel.style.height = panel.scrollHeight + 'px'; + panel.style.opacity = '1'; + } + var chevron = trigger.querySelector('.accordion-chevron'); + if (chevron) chevron.style.transform = expanded ? '' : 'rotate(180deg)'; + }); + }); + } + + function initAll() { + document.querySelectorAll('.accordion-root').forEach(initAccordion); + } + + document.addEventListener('DOMContentLoaded', initAll); + document.addEventListener('htmx:afterSwap', initAll); +})(); + + +// ── Toast ───────────────────────────────────────────────────────────────── + +(function () { + function dismissToast(toast) { + toast.style.opacity = '0'; + toast.style.transform = 'translateY(0.5rem)'; + setTimeout(function () { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300); + } + + window.showToast = function (options) { + var viewport = document.querySelector('.toast-viewport'); + if (!viewport) return; + + var title = options.title || ''; + var description = options.description || ''; + var variant = options.variant || 'default'; + var duration = typeof options.duration === 'number' ? options.duration : 5000; + + var variantCls = variant === 'destructive' ? ' border-destructive text-destructive' : ''; + + var toast = document.createElement('div'); + toast.setAttribute('role', 'alert'); + toast.className = 'toast-item pointer-events-auto relative flex w-full items-center justify-between' + + ' space-x-4 overflow-hidden rounded-md border border-border bg-background p-4' + + ' shadow-lg transition-all duration-300 opacity-0 translate-y-2' + variantCls; + + toast.innerHTML = + '