mirror of
https://github.com/axzilla/templui.git
synced 2025-03-13 10:53:35 +00:00
feat(rating): initial implementation
This commit is contained in:
parent
9e3ce2e6ea
commit
a871c15d35
assets/css
cmd/server
components
helpers
internal
@ -7,6 +7,7 @@
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--color-red-500: oklch(0.637 0.237 25.331);
|
||||
--color-yellow-400: oklch(0.852 0.199 91.936);
|
||||
--color-yellow-500: oklch(0.795 0.184 86.047);
|
||||
--color-green-500: oklch(0.723 0.219 149.579);
|
||||
--color-green-700: oklch(0.527 0.154 150.069);
|
||||
@ -772,6 +773,9 @@
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);
|
||||
}
|
||||
.cursor-default {
|
||||
cursor: default;
|
||||
}
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@ -1344,6 +1348,9 @@
|
||||
.text-white {
|
||||
color: var(--color-white);
|
||||
}
|
||||
.text-yellow-400 {
|
||||
color: var(--color-yellow-400);
|
||||
}
|
||||
.text-yellow-500 {
|
||||
color: var(--color-yellow-500);
|
||||
}
|
||||
@ -1365,6 +1372,9 @@
|
||||
.opacity-0 {
|
||||
opacity: 0%;
|
||||
}
|
||||
.opacity-30 {
|
||||
opacity: 30%;
|
||||
}
|
||||
.opacity-50 {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ func main() {
|
||||
mux.Handle("GET /docs/components/pagination", templ.Handler(pages.Pagination()))
|
||||
mux.Handle("GET /docs/components/radio", templ.Handler(pages.Radio()))
|
||||
mux.Handle("GET /docs/components/radio-card", templ.Handler(pages.RadioCard()))
|
||||
mux.Handle("GET /docs/components/rating", templ.Handler(pages.Rating()))
|
||||
mux.Handle("GET /docs/components/select", templ.Handler(pages.Select()))
|
||||
mux.Handle("GET /docs/components/sheet", templ.Handler(pages.Sheet()))
|
||||
mux.Handle("GET /docs/components/slider", templ.Handler(pages.Slider()))
|
||||
|
295
components/rating.templ
Normal file
295
components/rating.templ
Normal file
@ -0,0 +1,295 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/axzilla/templui/icons"
|
||||
"github.com/axzilla/templui/utils"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RatingSize defines available sizes for the Rating component
|
||||
type RatingSize string
|
||||
|
||||
const (
|
||||
RatingSizeSmall RatingSize = "small"
|
||||
RatingSizeMedium RatingSize = "medium"
|
||||
RatingSizeLarge RatingSize = "large"
|
||||
)
|
||||
|
||||
// RatingStyle defines the visual style for the Rating component
|
||||
type RatingStyle string
|
||||
|
||||
const (
|
||||
RatingStyleStar RatingStyle = "star" // Star icons for ratings
|
||||
RatingStyleHeart RatingStyle = "heart" // Heart icons for ratings
|
||||
RatingStyleEmoji RatingStyle = "emoji" // Emoji faces for ratings
|
||||
RatingStyleNumeric RatingStyle = "numeric" // Numeric display (with optional icon)
|
||||
)
|
||||
|
||||
// RatingProps configures the Rating component
|
||||
type RatingProps struct {
|
||||
Value float64 // Current rating value
|
||||
MaxValue int // Maximum rating value (default: 5)
|
||||
ReadOnly bool // Whether the rating is interactive or display-only
|
||||
Style RatingStyle // Visual style for the rating
|
||||
Size RatingSize // Size of the rating elements
|
||||
Precision float64 // Step precision for fractional ratings (0.5, 0.1, etc.)
|
||||
Label string // Optional label for the rating
|
||||
Name string // Form field name (when used in forms)
|
||||
ShowValue bool // Whether to display the numeric value
|
||||
OnlyInteger bool // Force ratings to integer values
|
||||
Class string // Additional CSS classes
|
||||
Attributes templ.Attributes // Additional HTML attributes
|
||||
}
|
||||
|
||||
// Default values for the Rating component
|
||||
func (p *RatingProps) setDefaults() {
|
||||
if p.MaxValue <= 0 {
|
||||
p.MaxValue = 5
|
||||
}
|
||||
if p.Precision <= 0 {
|
||||
p.Precision = 1.0
|
||||
}
|
||||
if p.Style == "" {
|
||||
p.Style = RatingStyleStar
|
||||
}
|
||||
if p.Size == "" {
|
||||
p.Size = RatingSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
// Get size class based on the RatingSize
|
||||
func getSizeClass(size RatingSize) string {
|
||||
switch size {
|
||||
case RatingSizeSmall:
|
||||
return "text-lg"
|
||||
case RatingSizeLarge:
|
||||
return "text-3xl"
|
||||
default: // Medium
|
||||
return "text-2xl"
|
||||
}
|
||||
}
|
||||
|
||||
// Get color class based on the RatingStyle
|
||||
func getColorClass(style RatingStyle) string {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return "text-destructive"
|
||||
case RatingStyleEmoji:
|
||||
return "text-yellow-500"
|
||||
default: // Star and others
|
||||
return "text-yellow-400"
|
||||
}
|
||||
}
|
||||
|
||||
// Get item icon based on the RatingStyle
|
||||
func getRatingIcon(style RatingStyle, filled bool, value float64) templ.Component {
|
||||
// Emoji style uses different icons based on value
|
||||
if style == RatingStyleEmoji {
|
||||
if filled {
|
||||
// Choose emoji based on rating level (1-5)
|
||||
switch {
|
||||
case value <= 1:
|
||||
return icons.Angry(icons.IconProps{})
|
||||
case value <= 2:
|
||||
return icons.Frown(icons.IconProps{})
|
||||
case value <= 3:
|
||||
return icons.Meh(icons.IconProps{})
|
||||
case value <= 4:
|
||||
return icons.Smile(icons.IconProps{})
|
||||
default:
|
||||
return icons.Laugh(icons.IconProps{})
|
||||
}
|
||||
}
|
||||
return icons.Meh(icons.IconProps{})
|
||||
}
|
||||
|
||||
// Handle other styles
|
||||
if filled {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return icons.Heart(icons.IconProps{Fill: "currentColor"})
|
||||
default: // Star
|
||||
return icons.Star(icons.IconProps{Fill: "currentColor"})
|
||||
}
|
||||
} else {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return icons.Heart(icons.IconProps{})
|
||||
default: // Star
|
||||
return icons.Star(icons.IconProps{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive Rating component with Alpine.js
|
||||
templ RatingScript() {
|
||||
{{ handle := templ.NewOnceHandle() }}
|
||||
@handle.Once() {
|
||||
<script defer nonce={ templ.GetNonce(ctx) }>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('rating', () => ({
|
||||
value: 0,
|
||||
maxValue: 5,
|
||||
precision: 1,
|
||||
readonly: false,
|
||||
name: '',
|
||||
onlyInteger: false,
|
||||
previewValue: 0,
|
||||
|
||||
init() {
|
||||
// Get configuration from data attributes
|
||||
this.value = parseFloat(this.$el.dataset.value) || 0;
|
||||
this.maxValue = parseInt(this.$el.dataset.maxvalue) || 5;
|
||||
this.precision = parseFloat(this.$el.dataset.precision) || 1;
|
||||
this.readonly = this.$el.dataset.readonly === 'true';
|
||||
this.name = this.$el.dataset.name || '';
|
||||
this.onlyInteger = this.$el.dataset.onlyinteger === 'true';
|
||||
|
||||
// Round value to the nearest step based on precision
|
||||
this.value = Math.round(this.value / this.precision) * this.precision;
|
||||
},
|
||||
|
||||
setValue() {
|
||||
if (this.readonly) return;
|
||||
|
||||
// Get the rating value from the clicked element's data attribute
|
||||
const item = this.$event.target.closest('[data-rating-value]');
|
||||
if (!item) return;
|
||||
|
||||
const newValue = parseInt(item.dataset.ratingValue);
|
||||
|
||||
// Handle precision for fractional ratings
|
||||
if (this.onlyInteger) {
|
||||
this.value = Math.round(newValue);
|
||||
} else {
|
||||
this.value = Math.round(newValue / this.precision) * this.precision;
|
||||
}
|
||||
|
||||
// Ensure value is within bounds
|
||||
this.value = Math.max(0, Math.min(this.maxValue, this.value));
|
||||
|
||||
// Dispatch event for form integration
|
||||
this.$dispatch('rating-change', {
|
||||
name: this.name,
|
||||
value: this.value
|
||||
});
|
||||
},
|
||||
|
||||
getFormattedValue() {
|
||||
// Format the value for display (rounded to 2 decimal places)
|
||||
return Math.round(this.value * 100) / 100;
|
||||
},
|
||||
|
||||
getItemStyle() {
|
||||
// Get the index from the element's data attribute
|
||||
const index = parseInt(this.$el.dataset.index || '0');
|
||||
|
||||
// Calculate item styling
|
||||
const filled = index <= Math.floor(this.value);
|
||||
const partial = !filled && (index - 1 < this.value && this.value < index);
|
||||
const percentage = partial ? (this.value - Math.floor(this.value)) * 100 : 0;
|
||||
|
||||
// Return appropriate width style
|
||||
return {
|
||||
width: filled ? '100%' : (partial ? percentage + '%' : '0%')
|
||||
};
|
||||
},
|
||||
|
||||
hover() {
|
||||
if (this.readonly) return;
|
||||
// Preview rating on hover
|
||||
const item = this.$event.target.closest('[data-rating-value]');
|
||||
if (!item) return;
|
||||
|
||||
this.previewValue = parseInt(item.dataset.ratingValue);
|
||||
},
|
||||
|
||||
getCursorClass() {
|
||||
return this.readonly ? 'cursor-default' : 'cursor-pointer';
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
// Rating component for user ratings and feedback
|
||||
templ Rating(props RatingProps) {
|
||||
// Apply default values
|
||||
{{ props.setDefaults() }}
|
||||
<div
|
||||
x-data="rating"
|
||||
data-value={ strconv.FormatFloat(props.Value, 'f', -1, 64) }
|
||||
data-maxvalue={ strconv.Itoa(props.MaxValue) }
|
||||
data-precision={ strconv.FormatFloat(props.Precision, 'f', -1, 64) }
|
||||
data-readonly={ strconv.FormatBool(props.ReadOnly) }
|
||||
data-name={ props.Name }
|
||||
data-onlyinteger={ strconv.FormatBool(props.OnlyInteger) }
|
||||
class={ utils.TwMerge(
|
||||
"flex flex-col items-start gap-1",
|
||||
props.Class,
|
||||
) }
|
||||
{ props.Attributes... }
|
||||
>
|
||||
if props.Label != "" {
|
||||
<label class="text-sm font-medium text-foreground">{ props.Label }</label>
|
||||
}
|
||||
<div class="flex items-center gap-1">
|
||||
if props.Style != RatingStyleNumeric {
|
||||
<!-- Standard rating items (stars, hearts, emojis) -->
|
||||
for i := 1; i <= props.MaxValue; i++ {
|
||||
<div
|
||||
class={
|
||||
utils.TwMerge(
|
||||
"relative",
|
||||
getSizeClass(props.Size),
|
||||
getColorClass(props.Style),
|
||||
"transition-opacity",
|
||||
),
|
||||
templ.KV("cursor-pointer", !props.ReadOnly),
|
||||
templ.KV("cursor-default", props.ReadOnly),
|
||||
}
|
||||
data-rating-value={ strconv.Itoa(i) }
|
||||
@click="setValue"
|
||||
@mouseover="hover"
|
||||
>
|
||||
<div class="opacity-30">
|
||||
@getRatingIcon(props.Style, false, float64(i))
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 overflow-hidden"
|
||||
x-bind:style="getItemStyle"
|
||||
data-index={ strconv.Itoa(i) }
|
||||
>
|
||||
@getRatingIcon(props.Style, true, float64(i))
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
<!-- Numeric display with optional icon -->
|
||||
<span class={ utils.TwMerge("font-bold", getSizeClass(props.Size)) }>
|
||||
<span x-text="getFormattedValue"></span>
|
||||
<span>/</span><span x-text="maxValue"></span>
|
||||
</span>
|
||||
<span class={ utils.TwMerge(getColorClass(RatingStyleStar), getSizeClass(props.Size)) }>
|
||||
@icons.Star(icons.IconProps{})
|
||||
</span>
|
||||
}
|
||||
<!-- Hidden input for form submission -->
|
||||
if props.Name != "" {
|
||||
<input
|
||||
type="hidden"
|
||||
name={ props.Name }
|
||||
x-bind:value="value"
|
||||
/>
|
||||
}
|
||||
<!-- Optional display of numeric value -->
|
||||
if props.ShowValue && props.Style != RatingStyleNumeric {
|
||||
<span class="ml-2 text-sm text-muted-foreground">
|
||||
<span x-text="getFormattedValue"></span>
|
||||
<span>/</span><span x-text="maxValue"></span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
530
components/rating_templ.go
Normal file
530
components/rating_templ.go
Normal file
@ -0,0 +1,530 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/axzilla/templui/icons"
|
||||
"github.com/axzilla/templui/utils"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RatingSize defines available sizes for the Rating component
|
||||
type RatingSize string
|
||||
|
||||
const (
|
||||
RatingSizeSmall RatingSize = "small"
|
||||
RatingSizeMedium RatingSize = "medium"
|
||||
RatingSizeLarge RatingSize = "large"
|
||||
)
|
||||
|
||||
// RatingStyle defines the visual style for the Rating component
|
||||
type RatingStyle string
|
||||
|
||||
const (
|
||||
RatingStyleStar RatingStyle = "star" // Star icons for ratings
|
||||
RatingStyleHeart RatingStyle = "heart" // Heart icons for ratings
|
||||
RatingStyleEmoji RatingStyle = "emoji" // Emoji faces for ratings
|
||||
RatingStyleNumeric RatingStyle = "numeric" // Numeric display (with optional icon)
|
||||
)
|
||||
|
||||
// RatingProps configures the Rating component
|
||||
type RatingProps struct {
|
||||
Value float64 // Current rating value
|
||||
MaxValue int // Maximum rating value (default: 5)
|
||||
ReadOnly bool // Whether the rating is interactive or display-only
|
||||
Style RatingStyle // Visual style for the rating
|
||||
Size RatingSize // Size of the rating elements
|
||||
Precision float64 // Step precision for fractional ratings (0.5, 0.1, etc.)
|
||||
Label string // Optional label for the rating
|
||||
Name string // Form field name (when used in forms)
|
||||
ShowValue bool // Whether to display the numeric value
|
||||
OnlyInteger bool // Force ratings to integer values
|
||||
Class string // Additional CSS classes
|
||||
Attributes templ.Attributes // Additional HTML attributes
|
||||
}
|
||||
|
||||
// Default values for the Rating component
|
||||
func (p *RatingProps) setDefaults() {
|
||||
if p.MaxValue <= 0 {
|
||||
p.MaxValue = 5
|
||||
}
|
||||
if p.Precision <= 0 {
|
||||
p.Precision = 1.0
|
||||
}
|
||||
if p.Style == "" {
|
||||
p.Style = RatingStyleStar
|
||||
}
|
||||
if p.Size == "" {
|
||||
p.Size = RatingSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
// Get size class based on the RatingSize
|
||||
func getSizeClass(size RatingSize) string {
|
||||
switch size {
|
||||
case RatingSizeSmall:
|
||||
return "text-lg"
|
||||
case RatingSizeLarge:
|
||||
return "text-3xl"
|
||||
default: // Medium
|
||||
return "text-2xl"
|
||||
}
|
||||
}
|
||||
|
||||
// Get color class based on the RatingStyle
|
||||
func getColorClass(style RatingStyle) string {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return "text-destructive"
|
||||
case RatingStyleEmoji:
|
||||
return "text-yellow-500"
|
||||
default: // Star and others
|
||||
return "text-yellow-400"
|
||||
}
|
||||
}
|
||||
|
||||
// Get item icon based on the RatingStyle
|
||||
func getRatingIcon(style RatingStyle, filled bool, value float64) templ.Component {
|
||||
// Emoji style uses different icons based on value
|
||||
if style == RatingStyleEmoji {
|
||||
if filled {
|
||||
// Choose emoji based on rating level (1-5)
|
||||
switch {
|
||||
case value <= 1:
|
||||
return icons.Angry(icons.IconProps{})
|
||||
case value <= 2:
|
||||
return icons.Frown(icons.IconProps{})
|
||||
case value <= 3:
|
||||
return icons.Meh(icons.IconProps{})
|
||||
case value <= 4:
|
||||
return icons.Smile(icons.IconProps{})
|
||||
default:
|
||||
return icons.Laugh(icons.IconProps{})
|
||||
}
|
||||
}
|
||||
return icons.Meh(icons.IconProps{})
|
||||
}
|
||||
|
||||
// Handle other styles
|
||||
if filled {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return icons.Heart(icons.IconProps{Fill: "currentColor"})
|
||||
default: // Star
|
||||
return icons.Star(icons.IconProps{Fill: "currentColor"})
|
||||
}
|
||||
} else {
|
||||
switch style {
|
||||
case RatingStyleHeart:
|
||||
return icons.Heart(icons.IconProps{})
|
||||
default: // Star
|
||||
return icons.Star(icons.IconProps{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interactive Rating component with Alpine.js
|
||||
func RatingScript() 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_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
handle := templ.NewOnceHandle()
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
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_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script defer nonce=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.GetNonce(ctx))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 128, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">\n document.addEventListener('alpine:init', () => {\n Alpine.data('rating', () => ({\n value: 0,\n maxValue: 5,\n precision: 1,\n readonly: false,\n name: '',\n onlyInteger: false,\n previewValue: 0,\n \n init() {\n // Get configuration from data attributes\n this.value = parseFloat(this.$el.dataset.value) || 0;\n this.maxValue = parseInt(this.$el.dataset.maxvalue) || 5;\n this.precision = parseFloat(this.$el.dataset.precision) || 1;\n this.readonly = this.$el.dataset.readonly === 'true';\n this.name = this.$el.dataset.name || '';\n this.onlyInteger = this.$el.dataset.onlyinteger === 'true';\n \n // Round value to the nearest step based on precision\n this.value = Math.round(this.value / this.precision) * this.precision;\n },\n \n setValue() {\n if (this.readonly) return;\n \n // Get the rating value from the clicked element's data attribute\n const item = this.$event.target.closest('[data-rating-value]');\n if (!item) return;\n \n const newValue = parseInt(item.dataset.ratingValue);\n \n // Handle precision for fractional ratings\n if (this.onlyInteger) {\n this.value = Math.round(newValue);\n } else {\n this.value = Math.round(newValue / this.precision) * this.precision;\n }\n \n // Ensure value is within bounds\n this.value = Math.max(0, Math.min(this.maxValue, this.value));\n \n // Dispatch event for form integration\n this.$dispatch('rating-change', { \n name: this.name, \n value: this.value \n });\n },\n \n getFormattedValue() {\n // Format the value for display (rounded to 2 decimal places)\n return Math.round(this.value * 100) / 100;\n },\n \n getItemStyle() {\n // Get the index from the element's data attribute\n const index = parseInt(this.$el.dataset.index || '0');\n \n // Calculate item styling\n const filled = index <= Math.floor(this.value);\n const partial = !filled && (index - 1 < this.value && this.value < index);\n const percentage = partial ? (this.value - Math.floor(this.value)) * 100 : 0;\n \n // Return appropriate width style\n return {\n width: filled ? '100%' : (partial ? percentage + '%' : '0%')\n };\n },\n \n hover() {\n if (this.readonly) return;\n // Preview rating on hover\n const item = this.$event.target.closest('[data-rating-value]');\n if (!item) return;\n \n this.previewValue = parseInt(item.dataset.ratingValue);\n },\n \n getCursorClass() {\n return this.readonly ? 'cursor-default' : 'cursor-pointer';\n }\n }));\n });\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = handle.Once().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Rating component for user ratings and feedback
|
||||
func Rating(props RatingProps) 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_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
props.setDefaults()
|
||||
var templ_7745c5c3_Var5 = []any{utils.TwMerge(
|
||||
"flex flex-col items-start gap-1",
|
||||
props.Class,
|
||||
)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div x-data=\"rating\" data-value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatFloat(props.Value, 'f', -1, 64))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 222, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" data-maxvalue=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(props.MaxValue))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 223, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" data-precision=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatFloat(props.Precision, 'f', -1, 64))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 224, Col: 68}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" data-readonly=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatBool(props.ReadOnly))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 225, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" data-name=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(props.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 226, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" data-onlyinteger=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatBool(props.OnlyInteger))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 227, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, props.Attributes)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if props.Label != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<label class=\"text-sm font-medium text-foreground\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(props.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 235, Col: 67}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</label>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flex items-center gap-1\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if props.Style != RatingStyleNumeric {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<!-- Standard rating items (stars, hearts, emojis) -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for i := 1; i <= props.MaxValue; i++ {
|
||||
var templ_7745c5c3_Var14 = []any{
|
||||
utils.TwMerge(
|
||||
"relative",
|
||||
getSizeClass(props.Size),
|
||||
getColorClass(props.Style),
|
||||
"transition-opacity",
|
||||
),
|
||||
templ.KV("cursor-pointer", !props.ReadOnly),
|
||||
templ.KV("cursor-default", props.ReadOnly),
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" data-rating-value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 252, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" @click=\"setValue\" @mouseover=\"hover\"><div class=\"opacity-30\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = getRatingIcon(props.Style, false, float64(i)).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div><div class=\"absolute inset-0 overflow-hidden\" x-bind:style=\"getItemStyle\" data-index=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 262, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = getRatingIcon(props.Style, true, float64(i)).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<!-- Numeric display with optional icon --> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 = []any{utils.TwMerge("font-bold", getSizeClass(props.Size))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var18).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"><span x-text=\"getFormattedValue\"></span> <span>/</span><span x-text=\"maxValue\"></span></span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 = []any{utils.TwMerge(getColorClass(RatingStyleStar), getSizeClass(props.Size))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var20).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = icons.Star(icons.IconProps{}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<!-- Hidden input for form submission -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if props.Name != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<input type=\"hidden\" name=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(props.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/rating.templ`, Line: 282, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" x-bind:value=\"value\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<!-- Optional display of numeric value -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if props.ShowValue && props.Style != RatingStyleNumeric {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<span class=\"ml-2 text-sm text-muted-foreground\"><span x-text=\"getFormattedValue\"></span> <span>/</span><span x-text=\"maxValue\"></span></span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -5,6 +5,7 @@ import "github.com/axzilla/templui/components"
|
||||
// ComponentScripts returns script tags for all components.
|
||||
templ ComponentScripts() {
|
||||
@components.ChartScripts()
|
||||
@components.RatingScript()
|
||||
@components.CarouselScript()
|
||||
@components.CodeScript()
|
||||
@components.ToastScript()
|
||||
|
@ -36,6 +36,10 @@ func ComponentScripts() templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.RatingScript().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.CarouselScript().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
|
@ -129,6 +129,10 @@ var Sections = []Section{
|
||||
Text: "RadioCard",
|
||||
Href: "/docs/components/radio-card",
|
||||
},
|
||||
{
|
||||
Text: "Rating",
|
||||
Href: "/docs/components/rating",
|
||||
},
|
||||
{
|
||||
Text: "Select",
|
||||
Href: "/docs/components/select",
|
||||
|
70
internal/ui/pages/rating.templ
Normal file
70
internal/ui/pages/rating.templ
Normal file
@ -0,0 +1,70 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"github.com/axzilla/templui/internal/ui/layouts"
|
||||
"github.com/axzilla/templui/internal/ui/modules"
|
||||
"github.com/axzilla/templui/internal/ui/showcase"
|
||||
)
|
||||
|
||||
templ Rating() {
|
||||
@layouts.DocsLayout(
|
||||
"Rating",
|
||||
"Interactive rating component for capturing user feedback and displaying scores.",
|
||||
) {
|
||||
@modules.PageWrapper(modules.PageWrapperProps{
|
||||
Name: "Rating",
|
||||
Description: templ.Raw("Interactive rating component for capturing user feedback and displaying scores."),
|
||||
Tailwind: true,
|
||||
Alpine: true,
|
||||
}) {
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
ShowcaseFile: showcase.RatingDefault(),
|
||||
PreviewCodeFile: "rating.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.Usage(modules.UsageProps{
|
||||
ComponentName: "Rating",
|
||||
NeedsHandler: true,
|
||||
PropsExample: "Value: 4.5, MaxValue: 5, ReadOnly: false, Style: components.RatingStyleStar",
|
||||
})
|
||||
@modules.ContainerWrapper(modules.ContainerWrapperProps{Title: "Examples"}) {
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Read-Only",
|
||||
ShowcaseFile: showcase.RatingReadOnly(),
|
||||
PreviewCodeFile: "rating_readonly.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Sizes",
|
||||
ShowcaseFile: showcase.RatingSizes(),
|
||||
PreviewCodeFile: "rating_sizes.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Styles",
|
||||
ShowcaseFile: showcase.RatingStyles(),
|
||||
PreviewCodeFile: "rating_styles.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Precision",
|
||||
ShowcaseFile: showcase.RatingPrecision(),
|
||||
PreviewCodeFile: "rating_precision.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Custom Max Values",
|
||||
ShowcaseFile: showcase.RatingMaxValues(),
|
||||
PreviewCodeFile: "rating_max_values.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
@modules.ExampleWrapper(modules.ExampleWrapperProps{
|
||||
SectionName: "Form Integration",
|
||||
ShowcaseFile: showcase.RatingForm(),
|
||||
PreviewCodeFile: "rating_form.templ",
|
||||
ComponentCodeFile: "rating.templ",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
227
internal/ui/showcase/rating.templ
Normal file
227
internal/ui/showcase/rating.templ
Normal file
@ -0,0 +1,227 @@
|
||||
package showcase
|
||||
|
||||
import "github.com/axzilla/templui/components"
|
||||
|
||||
// Basic star rating showcase
|
||||
templ RatingDefault() {
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3.5,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 0.5,
|
||||
ShowValue: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Read-only rating showcase
|
||||
templ RatingReadOnly() {
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4.7,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
})
|
||||
}
|
||||
|
||||
// Different sizes showcase
|
||||
templ RatingSizes() {
|
||||
<div class="flex flex-col gap-4">
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeSmall,
|
||||
Label: "Small",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "Medium",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeLarge,
|
||||
Label: "Large",
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
// Different styles showcase
|
||||
templ RatingStyles() {
|
||||
<div class="flex flex-col gap-4">
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "Star",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleHeart,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "Heart",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleEmoji,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "Emoji",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleNumeric,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "Numeric",
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
// Form integration showcase
|
||||
templ RatingForm() {
|
||||
<form class="max-w-sm mx-auto">
|
||||
@components.Card(components.CardProps{}) {
|
||||
@components.CardHeader() {
|
||||
@components.CardTitle() {
|
||||
Product Review
|
||||
}
|
||||
@components.CardDescription() {
|
||||
Please rate our service
|
||||
}
|
||||
}
|
||||
@components.CardContent() {
|
||||
@components.FormItem(components.FormItemProps{}) {
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 1.0,
|
||||
Label: "Product Quality",
|
||||
Name: "product_quality",
|
||||
})
|
||||
}
|
||||
@components.FormItem(components.FormItemProps{}) {
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 4,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleHeart,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 1.0,
|
||||
Label: "Customer Service",
|
||||
Name: "customer_service",
|
||||
})
|
||||
}
|
||||
@components.FormItem(components.FormItemProps{}) {
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 2.5,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleEmoji,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 0.5,
|
||||
Label: "Overall Experience",
|
||||
Name: "overall_experience",
|
||||
})
|
||||
}
|
||||
@components.FormItem(components.FormItemProps{}) {
|
||||
@components.Label(components.LabelProps{
|
||||
Text: "Additional Comments",
|
||||
For: "comments",
|
||||
})
|
||||
@components.Textarea(components.TextareaProps{
|
||||
ID: "comments",
|
||||
Name: "comments",
|
||||
Placeholder: "Tell us what you think...",
|
||||
Rows: 3,
|
||||
})
|
||||
}
|
||||
}
|
||||
@components.CardFooter() {
|
||||
@components.Button(components.ButtonProps{
|
||||
Type: "submit",
|
||||
Text: "Submit Review",
|
||||
})
|
||||
}
|
||||
}
|
||||
</form>
|
||||
}
|
||||
|
||||
// Custom precision showcase
|
||||
templ RatingPrecision() {
|
||||
<div class="flex flex-col gap-4">
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3.7,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 1.0,
|
||||
Label: "Integer Ratings",
|
||||
ShowValue: true,
|
||||
OnlyInteger: true,
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3.7,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 0.5,
|
||||
Label: "Half-Star Ratings",
|
||||
ShowValue: true,
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3.7,
|
||||
MaxValue: 5,
|
||||
ReadOnly: false,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Precision: 0.1,
|
||||
Label: "Decimal Ratings (0.1)",
|
||||
ShowValue: true,
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
// Custom max values showcase
|
||||
templ RatingMaxValues() {
|
||||
<div class="flex flex-col gap-4">
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 3,
|
||||
MaxValue: 3,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "3-Star Rating",
|
||||
})
|
||||
@components.Rating(components.RatingProps{
|
||||
Value: 7,
|
||||
MaxValue: 10,
|
||||
ReadOnly: true,
|
||||
Style: components.RatingStyleStar,
|
||||
Size: components.RatingSizeMedium,
|
||||
Label: "10-Star Rating",
|
||||
ShowValue: true,
|
||||
})
|
||||
</div>
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user