From a319540f3035abcf92bbdc5bccc7f0ba426e6d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Caxzilla=E2=80=9D?= Date: Mon, 7 Oct 2024 11:57:03 +0200 Subject: [PATCH] feat: add alert component --- CHANGELOG.md | 1 + assets/css/output.css | 248 ++++++++++++++++------------ cmd/server/main.go | 1 + internals/shared/menudata.go | 4 + internals/ui/components/alert.templ | 70 ++++++++ internals/ui/pages/alert.templ | 39 +++++ internals/ui/showcase/alert.templ | 48 ++++++ 7 files changed, 302 insertions(+), 109 deletions(-) create mode 100644 internals/ui/components/alert.templ create mode 100644 internals/ui/pages/alert.templ create mode 100644 internals/ui/showcase/alert.templ diff --git a/CHANGELOG.md b/CHANGELOG.md index 255857d..8e053e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2024-10-07 - Added: Input component +- Added: Alert component - Added: Accordion component - Added: Datepicker component - Changed: Add button type prop diff --git a/assets/css/output.css b/assets/css/output.css index b06780c..b958c9a 100644 --- a/assets/css/output.css +++ b/assets/css/output.css @@ -711,6 +711,10 @@ body { margin-right: auto; } +.mb-1 { + margin-bottom: 0.25rem; +} + .mb-12 { margin-bottom: 3rem; } @@ -751,6 +755,10 @@ body { margin-right: 0.5rem; } +.mt-12 { + margin-top: 3rem; +} + .mt-2 { margin-top: 0.5rem; } @@ -759,14 +767,6 @@ body { margin-top: 1rem; } -.mb-1 { - margin-bottom: 0.25rem; -} - -.mt-12 { - margin-top: 3rem; -} - .block { display: block; } @@ -815,6 +815,10 @@ body { height: 1.5rem; } +.h-7 { + height: 1.75rem; +} + .h-8 { height: 2rem; } @@ -831,10 +835,6 @@ body { height: 100%; } -.h-7 { - height: 1.75rem; -} - .\!max-h-\[501px\] { max-height: 501px !important; } @@ -871,14 +871,6 @@ body { width: 16rem; } -.w-\[350px\] { - width: 350px; -} - -.w-full { - width: 100%; -} - .w-7 { width: 1.75rem; } @@ -887,6 +879,14 @@ body { width: 17rem; } +.w-\[350px\] { + width: 350px; +} + +.w-full { + width: 100%; +} + .max-w-3xl { max-width: 48rem; } @@ -895,6 +895,10 @@ body { max-width: 80rem; } +.max-w-lg { + max-width: 32rem; +} + .max-w-md { max-width: 28rem; } @@ -907,10 +911,6 @@ body { max-width: 20rem; } -.max-w-lg { - max-width: 32rem; -} - .flex-1 { flex: 1 1 0%; } @@ -1017,10 +1017,6 @@ body { gap: 1rem; } -.gap-1 { - gap: 0.25rem; -} - .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -1102,6 +1098,10 @@ body { border-radius: 0.25rem; } +.rounded-full { + border-radius: 9999px; +} + .rounded-lg { border-radius: var(--radius); } @@ -1110,10 +1110,6 @@ body { border-radius: calc(var(--radius) - 2px); } -.rounded-full { - border-radius: 9999px; -} - .border { border-width: 1px; } @@ -1139,6 +1135,10 @@ body { border-color: hsl(var(--border) / var(--tw-border-opacity)); } +.border-destructive\/50 { + border-color: hsl(var(--destructive) / 0.5); +} + .border-input { --tw-border-opacity: 1; border-color: hsl(var(--input) / var(--tw-border-opacity)); @@ -1152,6 +1152,11 @@ body { border-color: transparent; } +.border-destructive { + --tw-border-opacity: 1; + border-color: hsl(var(--destructive) / var(--tw-border-opacity)); +} + .bg-background { --tw-bg-opacity: 1; background-color: hsl(var(--background) / var(--tw-bg-opacity)); @@ -1181,6 +1186,16 @@ body { background-color: hsl(var(--muted) / var(--tw-bg-opacity)); } +.bg-neutral-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 229 229 / var(--tw-bg-opacity)); +} + +.bg-neutral-800 { + --tw-bg-opacity: 1; + background-color: rgb(38 38 38 / var(--tw-bg-opacity)); +} + .bg-primary { --tw-bg-opacity: 1; background-color: hsl(var(--primary) / var(--tw-bg-opacity)); @@ -1196,31 +1211,6 @@ body { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-blue-100 { - --tw-bg-opacity: 1; - background-color: rgb(219 234 254 / var(--tw-bg-opacity)); -} - -.bg-blue-500 { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity)); -} - -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); -} - -.bg-neutral-200 { - --tw-bg-opacity: 1; - background-color: rgb(229 229 229 / var(--tw-bg-opacity)); -} - -.bg-neutral-800 { - --tw-bg-opacity: 1; - background-color: rgb(38 38 38 / var(--tw-bg-opacity)); -} - .bg-opacity-75 { --tw-bg-opacity: 0.75; } @@ -1272,6 +1262,11 @@ body { padding: 1.5rem; } +.px-0\.5 { + padding-left: 0.125rem; + padding-right: 0.125rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -1337,11 +1332,6 @@ body { padding-bottom: 2rem; } -.px-0\.5 { - padding-left: 0.125rem; - padding-right: 0.125rem; -} - .pb-4 { padding-bottom: 1rem; } @@ -1418,14 +1408,14 @@ body { font-weight: 500; } -.font-semibold { - font-weight: 600; -} - .font-normal { font-weight: 400; } +.font-semibold { + font-weight: 600; +} + .uppercase { text-transform: uppercase; } @@ -1448,6 +1438,11 @@ body { color: hsl(var(--card-foreground) / var(--tw-text-opacity)); } +.text-destructive { + --tw-text-opacity: 1; + color: hsl(var(--destructive) / var(--tw-text-opacity)); +} + .text-destructive-foreground { --tw-text-opacity: 1; color: hsl(var(--destructive-foreground) / var(--tw-text-opacity)); @@ -1473,6 +1468,11 @@ body { color: rgb(55 65 81 / var(--tw-text-opacity)); } +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + .text-gray-900 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity)); @@ -1483,6 +1483,11 @@ body { color: hsl(var(--muted-foreground) / var(--tw-text-opacity)); } +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} + .text-primary { --tw-text-opacity: 1; color: hsl(var(--primary) / var(--tw-text-opacity)); @@ -1503,16 +1508,6 @@ body { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-gray-800 { - --tw-text-opacity: 1; - color: rgb(31 41 55 / var(--tw-text-opacity)); -} - -.text-neutral-400 { - --tw-text-opacity: 1; - color: rgb(163 163 163 / var(--tw-text-opacity)); -} - .underline { text-decoration-line: underline; } @@ -1534,6 +1529,12 @@ body { opacity: 1; } +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .shadow-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); @@ -1546,12 +1547,6 @@ body { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow { - --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .outline { outline-style: solid; } @@ -1592,6 +1587,10 @@ body { transition-duration: 150ms; } +.duration-100 { + transition-duration: 100ms; +} + .duration-200 { transition-duration: 200ms; } @@ -1600,22 +1599,18 @@ body { transition-duration: 300ms; } -.duration-100 { - transition-duration: 100ms; -} - .ease-in { transition-timing-function: cubic-bezier(0.4, 0, 1, 1); } -.ease-out { - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); -} - .ease-in-out { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } +.ease-out { + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); +} + .\[mask-image\:linear-gradient\(180deg\2c white\2c rgba\(255\2c 255\2c 255\2c 0\)\)\] { -webkit-mask-image: linear-gradient(180deg,white,rgba(255,255,255,0)); mask-image: linear-gradient(180deg,white,rgba(255,255,255,0)); @@ -1672,6 +1667,11 @@ body { background-color: rgb(75 85 99 / var(--tw-bg-opacity)); } +.hover\:bg-neutral-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 229 229 / var(--tw-bg-opacity)); +} + .hover\:bg-primary\/90:hover { background-color: hsl(var(--primary) / 0.9); } @@ -1680,16 +1680,6 @@ body { background-color: hsl(var(--secondary) / 0.8); } -.hover\:bg-blue-100:hover { - --tw-bg-opacity: 1; - background-color: rgb(219 234 254 / var(--tw-bg-opacity)); -} - -.hover\:bg-neutral-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(229 229 229 / var(--tw-bg-opacity)); -} - .hover\:bg-opacity-75:hover { --tw-bg-opacity: 0.75; } @@ -1709,15 +1699,15 @@ body { color: rgb(107 114 128 / var(--tw-text-opacity)); } -.hover\:text-primary\/80:hover { - color: hsl(var(--primary) / 0.8); -} - .hover\:text-neutral-500:hover { --tw-text-opacity: 1; color: rgb(115 115 115 / var(--tw-text-opacity)); } +.hover\:text-primary\/80:hover { + color: hsl(var(--primary) / 0.8); +} + .hover\:underline:hover { text-decoration-line: underline; } @@ -1748,11 +1738,6 @@ body { --tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity)); } -.focus\:ring-blue-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); -} - .focus\:ring-ring:focus { --tw-ring-opacity: 1; --tw-ring-color: hsl(var(--ring) / var(--tw-ring-opacity)); @@ -1816,6 +1801,16 @@ body { opacity: 0.7; } +.dark\:border-destructive:is(.dark *) { + --tw-border-opacity: 1; + border-color: hsl(var(--destructive) / var(--tw-border-opacity)); +} + +.dark\:border-border:is(.dark *) { + --tw-border-opacity: 1; + border-color: hsl(var(--border) / var(--tw-border-opacity)); +} + .dark\:from-gray-900:is(.dark *) { --tw-gradient-from: #111827 var(--tw-gradient-from-position); --tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position); @@ -1846,6 +1841,11 @@ body { color: rgb(255 255 255 / var(--tw-text-opacity)); } +.dark\:text-destructive-foreground:is(.dark *) { + --tw-text-opacity: 1; + color: hsl(var(--destructive-foreground) / var(--tw-text-opacity)); +} + .dark\:file\:text-foreground:is(.dark *)::file-selector-button { --tw-text-opacity: 1; color: hsl(var(--foreground) / var(--tw-text-opacity)); @@ -1953,8 +1953,38 @@ body { } } +.\[\&\:has\(svg\)\]\:pl-11:has(svg) { + padding-left: 2.75rem; +} + +.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div { + --tw-translate-y: -3px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.\[\&\>svg\]\:absolute>svg { + position: absolute; +} + +.\[\&\>svg\]\:left-4>svg { + left: 1rem; +} + +.\[\&\>svg\]\:top-4>svg { + top: 1rem; +} + +.\[\&\>svg\]\:text-destructive>svg { + --tw-text-opacity: 1; + color: hsl(var(--destructive) / var(--tw-text-opacity)); +} + .\[\&\[aria-expanded\=true\]\>svg\]\:rotate-180[aria-expanded=true]>svg { --tw-rotate: 180deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.\[\&_p\]\:leading-relaxed p { + line-height: 1.625; +} + diff --git a/cmd/server/main.go b/cmd/server/main.go index a97d0bd..6b3a210 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -28,6 +28,7 @@ func main() { mux.Handle("GET /docs/components/input", templ.Handler(pages.Input())) mux.Handle("GET /docs/components/accordion", templ.Handler(pages.Accordion())) mux.Handle("GET /docs/components/datepicker", templ.Handler(pages.Datepicker())) + mux.Handle("GET /docs/components/alert", templ.Handler(pages.Alert())) fmt.Println("Server is running on http://localhost:8090") http.ListenAndServe(":8090", mux) diff --git a/internals/shared/menudata.go b/internals/shared/menudata.go index eed3bfa..08033b8 100644 --- a/internals/shared/menudata.go +++ b/internals/shared/menudata.go @@ -33,6 +33,10 @@ var Sections = []Section{ Text: "Accordion", Href: "/docs/components/accordion", }, + { + Text: "Alert", + Href: "/docs/components/alert", + }, { Text: "Button", Href: "/docs/components/button", diff --git a/internals/ui/components/alert.templ b/internals/ui/components/alert.templ new file mode 100644 index 0000000..99f209b --- /dev/null +++ b/internals/ui/components/alert.templ @@ -0,0 +1,70 @@ +package components + +// AlertVariant represents the visual style of the alert. +type AlertVariant string + +// Constants for alert variants. +const ( + DefaultAlert AlertVariant = "default" + DestructiveAlert AlertVariant = "destructive" +) + +// AlertProps defines the properties for the Alert component. +type AlertProps struct { + // Variant determines the visual style of the alert. + // Default: DefaultAlert + Variant AlertVariant + // Class specifies additional CSS classes to apply to the alert. + // Default: "" (empty string) + Class string +} + +// getAlertVariantClasses returns the CSS classes for the given alert variant. +func getAlertVariantClasses(variant AlertVariant) string { + switch variant { + case DestructiveAlert: + return "border-destructive text-destructive" + default: + return "border-border text-foreground" + } +} + +// Alert renders an alert component based on the provided props and children. +// It can be customized with two visual styles: Default and Destructive. +// All content, including icons, should be passed in as children. +// +// Usage: +// +// @components.Alert(components.AlertProps{Variant: components.DestructiveAlert}) { +// @components.ExclamationTriangleIcon() +// @components.AlertTitle{"Error"} +// @components.AlertDescription{"Your session has expired. Please log in again."} +// } +// +// Props: +// - Variant: The visual style of the alert (DefaultAlert or DestructiveAlert). Default: DefaultAlert +// - Class: Additional CSS classes to apply to the alert. Default: "" (empty string) +templ Alert(props AlertProps) { +
svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11", + getAlertVariantClasses(props.Variant), + props.Class } + role="alert" + > + { children... } +
+} + +// AlertTitle renders the title of the alert. +templ AlertTitle() { +
+ { children... } +
+} + +// AlertDescription renders the description of the alert. +templ AlertDescription() { +
+ { children... } +
+} diff --git a/internals/ui/pages/alert.templ b/internals/ui/pages/alert.templ new file mode 100644 index 0000000..11486f5 --- /dev/null +++ b/internals/ui/pages/alert.templ @@ -0,0 +1,39 @@ +package pages + +import ( + "github.com/axzilla/goilerplate/internals/ui/components" + "github.com/axzilla/goilerplate/internals/ui/layouts" + "github.com/axzilla/goilerplate/internals/ui/showcase" +) + +templ Alert() { + @layouts.DocsLayout() { +
+
+

Alert

+

Displays a callout for user attention, often used for notifications, warnings, or important messages.

+
+ @components.Tabs(components.TabsProps{ + Tabs: []components.Tab{ + { + ID: "preview", + Title: "Preview", + Content: showcase.AlertShowcase(), + }, + { + ID: "code", + Title: "Code", + Content: CodeSnippetFromEmbedded("alert.templ", "go", showcase.TemplFiles), + }, + { + ID: "component", + Title: "Component", + Content: CodeSnippetFromEmbedded("alert.templ", "go", components.TemplFiles), + }, + }, + TabsContainerClass: "md:w-1/2", + ContentContainerClass: "w-full", + }) +
+ } +} diff --git a/internals/ui/showcase/alert.templ b/internals/ui/showcase/alert.templ new file mode 100644 index 0000000..3fbcd6e --- /dev/null +++ b/internals/ui/showcase/alert.templ @@ -0,0 +1,48 @@ +package showcase + +import ( + "github.com/axzilla/goilerplate/internals/ui/components" +) + +templ RocketIcon() { + +} + +templ WarningIcon() { + +} + +templ AlertShowcase() { +
+
+
+

Default Alert

+
+ @components.Alert(components.AlertProps{Variant: components.DefaultAlert}) { + @RocketIcon() + @components.AlertTitle() { + Note + } + @components.AlertDescription() { + This is a default alert — check it out! + } + } +
+
+
+

Destructive Alert

+
+ @components.Alert(components.AlertProps{Variant: components.DestructiveAlert}) { + @WarningIcon() + @components.AlertTitle() { + Error + } + @components.AlertDescription() { + Your session has expired. Please log in again. + } + } +
+
+
+
+}