diff --git a/assets/css/output.css b/assets/css/output.css index a08d3c4..969c666 100644 --- a/assets/css/output.css +++ b/assets/css/output.css @@ -966,6 +966,10 @@ body { width: 4rem; } +.w-2\/3 { + width: 66.666667%; +} + .w-24 { width: 6rem; } @@ -998,10 +1002,6 @@ body { width: 2rem; } -.w-\[350px\] { - width: 350px; -} - .w-full { width: 100%; } @@ -1323,6 +1323,26 @@ body { border-radius: calc(var(--radius) - 4px); } +.rounded-b-lg { + border-bottom-right-radius: var(--radius); + border-bottom-left-radius: var(--radius); +} + +.rounded-l-lg { + border-top-left-radius: var(--radius); + border-bottom-left-radius: var(--radius); +} + +.rounded-r-lg { + border-top-right-radius: var(--radius); + border-bottom-right-radius: var(--radius); +} + +.rounded-t-lg { + border-top-left-radius: var(--radius); + border-top-right-radius: var(--radius); +} + .border { border-width: 1px; } diff --git a/assets/img/card_placeholder.jpeg b/assets/img/card_placeholder.jpeg new file mode 100644 index 0000000..df10648 Binary files /dev/null and b/assets/img/card_placeholder.jpeg differ diff --git a/internals/ui/showcase/card.templ b/internals/ui/showcase/card.templ index 93a70e0..5443540 100644 --- a/internals/ui/showcase/card.templ +++ b/internals/ui/showcase/card.templ @@ -1,24 +1,137 @@ package showcase -import "github.com/axzilla/goilerplate/pkg/components" +import ( + "github.com/axzilla/goilerplate/pkg/components" +) templ CardShowcase() { -
- @components.Card(components.CardProps{Class: "w-[350px]"}) { - @components.CardHeader() { - @components.CardTitle() { - Card Title +
+
+ // Default card +
+

Default

+ @components.Card(components.CardProps{Class: "w-2/3"}) { + @components.CardHeader() { + @components.CardTitle() { + Card Title + } + @components.CardDescription() { + Card Description + } + } + @components.CardContent() { +

Card Content

+ } + @components.CardFooter() { + @components.Button(components.ButtonProps{Text: "Action"}) + } } - @components.CardDescription() { - Card Description +
+ // Card with top image +
+

With Top Image

+ @components.Card(components.CardProps{Class: "w-2/3"}) { + @components.CardImage(components.CardImageProps{ + Src: "/assets/img/card_placeholder.jpeg", + Alt: "Card image", + Position: components.CardImageTop, + AspectRatio: "16/9", + }) + @components.CardHeader() { + @components.CardTitle() { + Featured Card + } + @components.CardDescription() { + With top image + } + } + @components.CardContent() { +

This card shows top image usage.

+ } + @components.CardFooter() { + @components.Button(components.ButtonProps{Text: "Learn More"}) + } } - } - @components.CardContent() { -

Card Content

- } - @components.CardFooter() { - @components.Button(components.ButtonProps{Text: "Action"}) - } - } +
+ // Card with bottom image +
+

With Bottom Image

+ @components.Card(components.CardProps{Class: "w-2/3"}) { + @components.CardHeader() { + @components.CardTitle() { + Featured Card + } + @components.CardDescription() { + With top image + } + } + @components.CardContent() { +

This card shows top image usage.

+ } + @components.CardFooter() { + @components.Button(components.ButtonProps{Text: "Learn More"}) + } + @components.CardImage(components.CardImageProps{ + Src: "/assets/img/card_placeholder.jpeg", + Alt: "Card image", + Position: components.CardImageBottom, + AspectRatio: "16/9", + }) + } +
+ // Horizontal card with left image +
+

Image Left

+ @components.Card(components.CardProps{ + Horizontal: true, + }) { + @components.CardImage(components.CardImageProps{ + Src: "/assets/img/card_placeholder.jpeg", + Alt: "Left side image", + Position: components.CardImageLeft, + Width: "1/3", + }) +
+ @components.CardHeader() { + @components.CardTitle() { + Side Image Card + } + @components.CardDescription() { + With left-aligned image + } + } + @components.CardContent() { +

This card demonstrates the left image layout.

+ } +
+ } +
+ // Horizontal card with right image +
+

Image Right

+ @components.Card(components.CardProps{ + Horizontal: true, + }) { +
+ @components.CardHeader() { + @components.CardTitle() { + Side Image Card + } + @components.CardDescription() { + With right-aligned image + } + } + @components.CardContent() { +

This card demonstrates the right image layout.

+ } +
+ @components.CardImage(components.CardImageProps{ + Src: "/assets/img/card_placeholder.jpeg", + Alt: "Right side image", + Position: components.CardImageRight, + }) + } +
+
} diff --git a/pkg/components/card.templ b/pkg/components/card.templ index 41e5b02..7923214 100644 --- a/pkg/components/card.templ +++ b/pkg/components/card.templ @@ -2,63 +2,138 @@ package components import "github.com/axzilla/goilerplate/pkg/utils" +// CardImagePosition defines where the image should be placed in the card +type CardImagePosition string + +const ( + CardImageTop CardImagePosition = "top" + CardImageBottom CardImagePosition = "bottom" + CardImageLeft CardImagePosition = "left" + CardImageRight CardImagePosition = "right" +) + type CardProps struct { - // Class adds custom CSS classes + // Class specifies additional CSS classes to apply to the card. + // Default: "" (empty string) Class string - // Attributes for additional HTML attributes + // Attributes allows passing additional HTML attributes to the card element. + // Default: nil Attributes templ.Attributes + + // Horizontal enables horizontal layout for side images + // Default: false + Horizontal bool } -// Card renders a container component with consistent styling and structure. +// Card renders a card component with consistent styling and structure. // For detailed examples and usage guides, visit https://goilerplate.com/docs/components/card // // Props: -// - Class: Additional CSS classes -// - Attributes: Additional HTML attributes +// - Class: Additional CSS classes to apply to the card. Default: "" (empty string) +// - Attributes: Additional HTML attributes to apply to the card element. Default: nil +// - Horizontal: Enables horizontal layout for side images. Default: false templ Card(props CardProps) {
{ children... }
} -// CardHeader renders the top section of the card -// Typically contains title and description +type CardImageProps struct { + // Src is the source URL of the image + Src string + + // Alt is the alternative text for the image + Alt string + + // Class allows adding custom classes to the image container + Class string + + // AspectRatio sets the aspect ratio of the image (e.g., "16/9", "4/3", "1/1") + AspectRatio string + + // Position determines the position of the image in the card + Position CardImagePosition + + // Width sets the width for side images (e.g., "1/3", "1/2") + Width string +} + +// getImageWidth returns the appropriate width class +func getImageWidth(width string) string { + if width == "" { + return "w-1/3" + } + return "w-" + width +} + +// CardImage renders an image section within the card +templ CardImage(props CardImageProps) { +
+ { +
+} + +// CardHeader renders the header section of a card. templ CardHeader() {
{ children... }
} -// CardTitle renders the card's main heading -// Uses h3 with consistent styling +// CardTitle renders the title of a card. templ CardTitle() { -

+

{ children... }

} -// CardDescription renders secondary text below the title -// Uses muted styling for visual hierarchy +// CardDescription renders the description of a card. templ CardDescription() {

{ children... }

} -// CardContent renders the main card body section -// Contains the primary content area +// CardContent renders the main content area of a card. templ CardContent() {
{ children... }
} -// CardFooter renders the bottom section of the card -// Typically contains actions or summary information +// CardFooter renders the footer section of a card. templ CardFooter() {
{ children... } diff --git a/pkg/components/card_templ.go b/pkg/components/card_templ.go index 0ec1966..8c009c3 100644 --- a/pkg/components/card_templ.go +++ b/pkg/components/card_templ.go @@ -10,20 +10,37 @@ import templruntime "github.com/a-h/templ/runtime" import "github.com/axzilla/goilerplate/pkg/utils" +// CardImagePosition defines where the image should be placed in the card +type CardImagePosition string + +const ( + CardImageTop CardImagePosition = "top" + CardImageBottom CardImagePosition = "bottom" + CardImageLeft CardImagePosition = "left" + CardImageRight CardImagePosition = "right" +) + type CardProps struct { - // Class adds custom CSS classes + // Class specifies additional CSS classes to apply to the card. + // Default: "" (empty string) Class string - // Attributes for additional HTML attributes + // Attributes allows passing additional HTML attributes to the card element. + // Default: nil Attributes templ.Attributes + + // Horizontal enables horizontal layout for side images + // Default: false + Horizontal bool } -// Card renders a container component with consistent styling and structure. +// Card renders a card component with consistent styling and structure. // For detailed examples and usage guides, visit https://goilerplate.com/docs/components/card // // Props: -// - Class: Additional CSS classes -// - Attributes: Additional HTML attributes +// - Class: Additional CSS classes to apply to the card. Default: "" (empty string) +// - Attributes: Additional HTML attributes to apply to the card element. Default: nil +// - Horizontal: Enables horizontal layout for side images. Default: false func Card(props CardProps) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -45,7 +62,13 @@ func Card(props CardProps) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var2 = []any{utils.TwMerge("rounded-lg border bg-card text-card-foreground shadow-sm", props.Class)} + var templ_7745c5c3_Var2 = []any{ + utils.TwMerge( + "rounded-lg border bg-card text-card-foreground shadow-sm", + props.Class, + ), + templ.KV("flex overflow-hidden", props.Horizontal), + } templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -87,9 +110,36 @@ func Card(props CardProps) templ.Component { }) } -// CardHeader renders the top section of the card -// Typically contains title and description -func CardHeader() templ.Component { +type CardImageProps struct { + // Src is the source URL of the image + Src string + + // Alt is the alternative text for the image + Alt string + + // Class allows adding custom classes to the image container + Class string + + // AspectRatio sets the aspect ratio of the image (e.g., "16/9", "4/3", "1/1") + AspectRatio string + + // Position determines the position of the image in the card + Position CardImagePosition + + // Width sets the width for side images (e.g., "1/3", "1/2") + Width string +} + +// getImageWidth returns the appropriate width class +func getImageWidth(width string) string { + if width == "" { + return "w-1/3" + } + return "w-" + width +} + +// CardImage renders an image section within the card +func CardImage(props CardImageProps) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -110,11 +160,121 @@ func CardHeader() templ.Component { templ_7745c5c3_Var4 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var5 = []any{ + utils.TwMerge( + "overflow-hidden", + props.Class, + ), + // Border radius based on position + templ.KV("rounded-t-lg", props.Position == CardImageTop), + templ.KV("rounded-b-lg", props.Position == CardImageBottom), + templ.KV("rounded-l-lg", props.Position == CardImageLeft), + templ.KV("rounded-r-lg", props.Position == CardImageRight), + // Width for side images + templ.KV("shrink-0", props.Position == CardImageLeft || props.Position == CardImageRight), + templ.KV(getImageWidth(props.Width), props.Position == CardImageLeft || props.Position == CardImageRight), + } + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
\"")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +// CardHeader renders the header section of a card. +func CardHeader() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templ_7745c5c3_Var10.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -126,8 +286,7 @@ func CardHeader() templ.Component { }) } -// CardTitle renders the card's main heading -// Uses h3 with consistent styling +// CardTitle renders the title of a card. func CardTitle() templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -144,16 +303,16 @@ func CardTitle() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var5 := templ.GetChildren(ctx) - if templ_7745c5c3_Var5 == nil { - templ_7745c5c3_Var5 = templ.NopComponent + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ_7745c5c3_Var5.Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templ_7745c5c3_Var11.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -165,8 +324,7 @@ func CardTitle() templ.Component { }) } -// CardDescription renders secondary text below the title -// Uses muted styling for visual hierarchy +// CardDescription renders the description of a card. func CardDescription() templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -183,16 +341,16 @@ func CardDescription() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templ_7745c5c3_Var12.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -204,8 +362,7 @@ func CardDescription() templ.Component { }) } -// CardContent renders the main card body section -// Contains the primary content area +// CardContent renders the main content area of a card. func CardContent() templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -222,16 +379,16 @@ func CardContent() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var7 := templ.GetChildren(ctx) - if templ_7745c5c3_Var7 == nil { - templ_7745c5c3_Var7 = templ.NopComponent + templ_7745c5c3_Var13 := templ.GetChildren(ctx) + if templ_7745c5c3_Var13 == nil { + templ_7745c5c3_Var13 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ_7745c5c3_Var7.Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templ_7745c5c3_Var13.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -243,8 +400,7 @@ func CardContent() templ.Component { }) } -// CardFooter renders the bottom section of the card -// Typically contains actions or summary information +// CardFooter renders the footer section of a card. func CardFooter() templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -261,16 +417,16 @@ func CardFooter() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ_7745c5c3_Var8.Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templ_7745c5c3_Var14.Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/styles/goilerplate.css b/pkg/styles/goilerplate.css index ed55fd9..828519a 100644 --- a/pkg/styles/goilerplate.css +++ b/pkg/styles/goilerplate.css @@ -938,6 +938,10 @@ body { flex-shrink: 1; } +.shrink-0 { + flex-shrink: 0; +} + .-translate-x-1\/2 { --tw-translate-x: -50%; 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)); @@ -1132,6 +1136,26 @@ body { border-radius: calc(var(--radius) - 4px); } +.rounded-b-lg { + border-bottom-right-radius: var(--radius); + border-bottom-left-radius: var(--radius); +} + +.rounded-l-lg { + border-top-left-radius: var(--radius); + border-bottom-left-radius: var(--radius); +} + +.rounded-r-lg { + border-top-right-radius: var(--radius); + border-bottom-right-radius: var(--radius); +} + +.rounded-t-lg { + border-top-left-radius: var(--radius); + border-top-right-radius: var(--radius); +} + .border { border-width: 1px; } @@ -1347,6 +1371,11 @@ body { text-align: center; } +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + .text-base { font-size: 1rem; line-height: 1.5rem;