├── .dockerignore ├── .env.example ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ └── validate-html.yml ├── .gitignore ├── .vscode └── launch.json ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── assets ├── assets.go ├── css │ ├── input.css │ └── themes.css ├── img │ ├── aspect_ratio_placeholder.jpeg │ ├── card_placeholder.jpeg │ ├── demo │ │ ├── carousel-1.jpeg │ │ ├── carousel-2.jpeg │ │ └── carousel-3.jpeg │ ├── favicon.svg │ ├── gopher.svg │ ├── readme.png │ ├── social-preview.png │ ├── tailwindcss-mark.svg │ └── templ-mark.svg └── js │ └── templui.min.js ├── cmd ├── docs │ └── main.go ├── icongen │ └── main.go ├── render-showcases │ └── main.go ├── sitemap │ └── main.go └── templui │ └── main.go ├── docker-compose.yml ├── eslint.config.js ├── go.mod ├── go.sum ├── internal ├── components │ ├── accordion │ │ └── accordion.templ │ ├── alert │ │ └── alert.templ │ ├── aspectratio │ │ └── aspectratio.templ │ ├── avatar │ │ ├── avatar.js │ │ ├── avatar.min.js │ │ └── avatar.templ │ ├── badge │ │ └── badge.templ │ ├── breadcrumb │ │ └── breadcrumb.templ │ ├── button │ │ └── button.templ │ ├── calendar │ │ ├── calendar.js │ │ ├── calendar.min.js │ │ └── calendar.templ │ ├── card │ │ └── card.templ │ ├── carousel │ │ ├── carousel.js │ │ ├── carousel.min.js │ │ └── carousel.templ │ ├── chart │ │ ├── chart.js │ │ ├── chart.min.js │ │ ├── chart.templ │ │ └── chartjs.js │ ├── checkbox │ │ └── checkbox.templ │ ├── checkboxcard │ │ └── checkboxcard.templ │ ├── code │ │ ├── code.js │ │ ├── code.min.js │ │ ├── code.templ │ │ └── highlight.js │ ├── datepicker │ │ ├── datepicker.js │ │ ├── datepicker.min.js │ │ └── datepicker.templ │ ├── drawer │ │ ├── drawer.js │ │ ├── drawer.min.js │ │ └── drawer.templ │ ├── dropdown │ │ ├── dropdown.js │ │ ├── dropdown.min.js │ │ └── dropdown.templ │ ├── embed.go │ ├── form │ │ └── form.templ │ ├── icon │ │ ├── icon.go │ │ ├── icondata.go │ │ └── icondefs.go │ ├── input │ │ ├── input.js │ │ ├── input.min.js │ │ └── input.templ │ ├── inputotp │ │ ├── inputotp.js │ │ ├── inputotp.min.js │ │ └── inputotp.templ │ ├── label │ │ ├── label.js │ │ ├── label.min.js │ │ └── label.templ │ ├── main.js │ ├── modal │ │ ├── modal.js │ │ ├── modal.min.js │ │ └── modal.templ │ ├── pagination │ │ └── pagination.templ │ ├── popover │ │ ├── floating_ui_core.js │ │ ├── floating_ui_dom.js │ │ ├── popover.js │ │ ├── popover.min.js │ │ └── popover.templ │ ├── progress │ │ ├── progress.js │ │ ├── progress.min.js │ │ └── progress.templ │ ├── radio │ │ └── radio.templ │ ├── radiocard │ │ └── radiocard.templ │ ├── rating │ │ ├── rating.js │ │ ├── rating.min.js │ │ └── rating.templ │ ├── selectbox │ │ ├── selectbox.js │ │ ├── selectbox.min.js │ │ └── selectbox.templ │ ├── separator │ │ └── separator.templ │ ├── skeleton │ │ └── skeleton.templ │ ├── slider │ │ ├── slider.js │ │ ├── slider.min.js │ │ └── slider.templ │ ├── spinner │ │ └── spinner.templ │ ├── table │ │ └── table.templ │ ├── tabs │ │ ├── tabs.js │ │ ├── tabs.min.js │ │ └── tabs.templ │ ├── textarea │ │ ├── textarea.js │ │ ├── textarea.min.js │ │ └── textarea.templ │ ├── timepicker │ │ └── timepicker.templ │ ├── toast │ │ ├── toast.js │ │ ├── toast.min.js │ │ └── toast.templ │ ├── toggle │ │ └── toggle.templ │ └── tooltip │ │ └── tooltip.templ ├── config │ └── config.go ├── ctxkeys │ └── ctxkeys.go ├── manifest.json ├── middleware │ └── middleware.go ├── shared │ └── menudata.go ├── ui │ ├── icons │ │ ├── github.templ │ │ ├── js.templ │ │ ├── tailwind.templ │ │ ├── templui.templ │ │ └── x.templ │ ├── layouts │ │ ├── base.templ │ │ └── docs.templ │ ├── modules │ │ ├── announcement_bar.templ │ │ ├── code.templ │ │ ├── codesnippet_embed.templ │ │ ├── component_usage.templ │ │ ├── container_wrapper.templ │ │ ├── example_wrapper.templ │ │ ├── footer.templ │ │ ├── navbar.templ │ │ ├── page_wrapper.templ │ │ ├── seo.templ │ │ ├── sidebar.templ │ │ ├── table_of_contents.templ │ │ └── themeswitcher.templ │ ├── pages │ │ ├── accordion.templ │ │ ├── alert.templ │ │ ├── aspect_ratio.templ │ │ ├── avatar.templ │ │ ├── badge.templ │ │ ├── breadcrumb.templ │ │ ├── button.templ │ │ ├── calendar.templ │ │ ├── card.templ │ │ ├── carousel.templ │ │ ├── chart.templ │ │ ├── checkbox.templ │ │ ├── checkbox_card.templ │ │ ├── code.templ │ │ ├── date_picker.templ │ │ ├── drawer.templ │ │ ├── dropdown.templ │ │ ├── form.templ │ │ ├── how_to_use.templ │ │ ├── icon.templ │ │ ├── input-otp.templ │ │ ├── input.templ │ │ ├── introduction.templ │ │ ├── label.templ │ │ ├── landing.templ │ │ ├── modal.templ │ │ ├── pagination.templ │ │ ├── popover.templ │ │ ├── progress.templ │ │ ├── radio.templ │ │ ├── radio_card.templ │ │ ├── rating.templ │ │ ├── select_box.templ │ │ ├── separator.templ │ │ ├── skeleton.templ │ │ ├── slider.templ │ │ ├── spinner.templ │ │ ├── table.templ │ │ ├── tabs.templ │ │ ├── textarea.templ │ │ ├── themes.templ │ │ ├── time_picker.templ │ │ ├── toast.templ │ │ ├── toggle.templ │ │ └── tooltip.templ │ └── showcase │ │ ├── accordion_default.templ │ │ ├── alert_default.templ │ │ ├── alert_destructive.templ │ │ ├── aspect_ratio_default.templ │ │ ├── avatar_default.templ │ │ ├── avatar_fallback.templ │ │ ├── avatar_group.templ │ │ ├── avatar_sizes.templ │ │ ├── avatar_with_icon.templ │ │ ├── badge_default.templ │ │ ├── badge_destructive.templ │ │ ├── badge_outline.templ │ │ ├── badge_secondary.templ │ │ ├── badge_with_icon.templ │ │ ├── breadcrumb_custom_separator.templ │ │ ├── breadcrumb_default.templ │ │ ├── breadcrumb_responsive.templ │ │ ├── breadcrumb_with_icons.templ │ │ ├── button_default.templ │ │ ├── button_destructive.templ │ │ ├── button_ghost.templ │ │ ├── button_htmx_loading.templ │ │ ├── button_icon.templ │ │ ├── button_link.templ │ │ ├── button_loading.templ │ │ ├── button_outline.templ │ │ ├── button_primary.templ │ │ ├── button_secondary.templ │ │ ├── button_with_icon.templ │ │ ├── calendar_default.templ │ │ ├── card_default.templ │ │ ├── card_image_bottom.templ │ │ ├── card_image_left.templ │ │ ├── card_image_right.templ │ │ ├── card_image_top.templ │ │ ├── carousel_autoplay.templ │ │ ├── carousel_default.templ │ │ ├── carousel_minimal.templ │ │ ├── carousel_with_images.templ │ │ ├── chart_area.templ │ │ ├── chart_area_linear.templ │ │ ├── chart_area_stacked.templ │ │ ├── chart_area_step.templ │ │ ├── chart_bar_horizontal.templ │ │ ├── chart_bar_multiple.templ │ │ ├── chart_bar_negative.templ │ │ ├── chart_bar_stacked.templ │ │ ├── chart_default.templ │ │ ├── chart_doughnut.templ │ │ ├── chart_doughnut_legend.templ │ │ ├── chart_doughnut_stacked.templ │ │ ├── chart_line.templ │ │ ├── chart_line_linear.templ │ │ ├── chart_line_multiple.templ │ │ ├── chart_line_step.templ │ │ ├── chart_pie.templ │ │ ├── chart_pie_legend.templ │ │ ├── chart_pie_stacked.templ │ │ ├── chart_radar.templ │ │ ├── chart_radar_stacked.templ │ │ ├── checkbox_card_default.templ │ │ ├── checkbox_checked.templ │ │ ├── checkbox_custom_icon.templ │ │ ├── checkbox_default.templ │ │ ├── checkbox_disabled.templ │ │ ├── checkbox_form.templ │ │ ├── checkbox_with_label.templ │ │ ├── code_copy_button.templ │ │ ├── code_custom_size.templ │ │ ├── code_default.templ │ │ ├── date_picker_custom_placeholder.templ │ │ ├── date_picker_default.templ │ │ ├── date_picker_disabled.templ │ │ ├── date_picker_form.templ │ │ ├── date_picker_formats.templ │ │ ├── date_picker_selected_date.templ │ │ ├── date_picker_with_label.templ │ │ ├── drawer_default.templ │ │ ├── drawer_positions.templ │ │ ├── dropdown_default.templ │ │ ├── embed.go │ │ ├── icon_colored.templ │ │ ├── icon_default.templ │ │ ├── icon_filled.templ │ │ ├── icon_sizes.templ │ │ ├── input_default.templ │ │ ├── input_disabled.templ │ │ ├── input_file.templ │ │ ├── input_form.templ │ │ ├── input_otp_custom_length.templ │ │ ├── input_otp_custom_styling.templ │ │ ├── input_otp_default.templ │ │ ├── input_otp_form.templ │ │ ├── input_otp_password_type.templ │ │ ├── input_otp_placeholder.templ │ │ ├── input_otp_with_label.templ │ │ ├── input_password.templ │ │ ├── input_with_label.templ │ │ ├── modal_default.templ │ │ ├── pagination_default.templ │ │ ├── pagination_with_helper.templ │ │ ├── popover_default.templ │ │ ├── popover_positions.templ │ │ ├── popover_triggers.templ │ │ ├── progress_colors.templ │ │ ├── progress_default.templ │ │ ├── progress_sizes.templ │ │ ├── radio_card_default.templ │ │ ├── radio_checked.templ │ │ ├── radio_default.templ │ │ ├── radio_disabled.templ │ │ ├── radio_form.templ │ │ ├── radio_with_label.templ │ │ ├── rating_default.templ │ │ ├── rating_form.templ │ │ ├── rating_max_values.templ │ │ ├── rating_precision.templ │ │ ├── rating_styles.templ │ │ ├── rating_with_label.templ │ │ ├── select_box_default.templ │ │ ├── select_box_disabled.templ │ │ ├── select_box_form.templ │ │ ├── select_box_with_label.templ │ │ ├── separator_decorated.templ │ │ ├── separator_default.templ │ │ ├── separator_label.templ │ │ ├── separator_vertical.templ │ │ ├── skeleton_card.templ │ │ ├── skeleton_dashboard.templ │ │ ├── skeleton_default.templ │ │ ├── skeleton_profile.templ │ │ ├── slider_default.templ │ │ ├── slider_disabled.templ │ │ ├── slider_external_value.templ │ │ ├── slider_steps.templ │ │ ├── slider_value.templ │ │ ├── spinner_colors.templ │ │ ├── spinner_default.templ │ │ ├── spinner_in_button.templ │ │ ├── spinner_sizes.templ │ │ ├── table.templ │ │ ├── tabs_default.templ │ │ ├── textarea_auto_resize.templ │ │ ├── textarea_custom_rows.templ │ │ ├── textarea_default.templ │ │ ├── textarea_disabled.templ │ │ ├── textarea_form.templ │ │ ├── textarea_with_label.templ │ │ ├── time_picker_12hour.templ │ │ ├── time_picker_custom_placeholder.templ │ │ ├── time_picker_default.templ │ │ ├── time_picker_form.templ │ │ ├── time_picker_label.templ │ │ ├── time_picker_selected_time.templ │ │ ├── toast_default.templ │ │ ├── toast_playground.templ │ │ ├── toggle_checked.templ │ │ ├── toggle_default.templ │ │ ├── toggle_disabled.templ │ │ ├── toggle_form.templ │ │ ├── toggle_with_label.templ │ │ ├── tooltip_default.templ │ │ └── tooltip_positions.templ └── utils │ ├── internal.go │ └── templui.go ├── meta.json ├── middleware └── csp.go ├── package-lock.json ├── package.json ├── shiki ├── Dockerfile ├── package-lock.json ├── package.json └── server.js └── static ├── robots.txt ├── sitemap.xml └── static.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules folder 2 | node_modules 3 | 4 | # Ignore logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Ignore dotenv environment variable files 10 | .env 11 | 12 | # Ignore local development configuration files 13 | config/local.yml 14 | 15 | # Ignore temporary files and directories 16 | tmp/ 17 | temp/ 18 | *.tmp 19 | 20 | # Ignore build and cache directories 21 | build/ 22 | dist/ 23 | .cache/ 24 | 25 | # Ignore OS generated files 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # Ignore version control directories and files 30 | .git 31 | .gitignore 32 | .gitmodules 33 | .hg 34 | .hgignore 35 | .hgsubstate 36 | .svn 37 | .cvsignore 38 | 39 | # Ignore Python files 40 | *.pyc 41 | *.pyo 42 | __pycache__/ 43 | 44 | # Ignore compiled source files 45 | *.o 46 | *.obj 47 | *.dll 48 | *.exe 49 | *.out 50 | 51 | # Ignore images and other binary files 52 | *.png 53 | *.jpg 54 | *.jpeg 55 | *.gif 56 | *.bmp 57 | *.tiff 58 | *.ico 59 | 60 | # Ignore compressed files 61 | *.zip 62 | *.tar.gz 63 | *.rar 64 | *.7z 65 | *.bz2 66 | 67 | # Ignore various IDE project files 68 | .vscode/ 69 | .idea/ 70 | *.suo 71 | *.user 72 | *.userossc 73 | *.sln.docstates 74 | .project 75 | .classpath 76 | .settings/ 77 | 78 | # Ignore documentation files 79 | *.md 80 | *.pdf 81 | 82 | # Ignore build output files 83 | *.war 84 | *.jar 85 | *.ear 86 | 87 | # Ignore Kubernetes and Docker Compose files 88 | k8s/ 89 | docker-compose.yml 90 | docker-compose.override.yml 91 | 92 | # Ignore other sensitive files 93 | *.pem 94 | *.key 95 | *.crt 96 | *.kdb 97 | *.jks 98 | 99 | # Ignore IntelliJ IDEA files 100 | *.iml 101 | *.iws 102 | .idea/ 103 | 104 | # Ignore JetBrains Rider files 105 | *.sln.iml 106 | .idea/ 107 | 108 | # Ignore WebStorm files 109 | .idea/ 110 | 111 | # Ignore Visual Studio Code files 112 | .vscode/ 113 | 114 | # Ignore SASS cache files 115 | .sass-cache/ 116 | 117 | # Ignore Bower files 118 | .bower/ 119 | 120 | # Ignore Gulp files 121 | .gulp/ 122 | 123 | # Ignore Grunt files 124 | .grunt/ 125 | 126 | # Ignore Yeoman files 127 | .yeoman/ 128 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GO_ENV=development 2 | SHIKI_URL=http://localhost:3000/highlight -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [axzilla] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug or unexpected behavior in templUI 4 | title: "[Bug] " 5 | labels: bug 6 | --- 7 | 8 | ## Describe the issue 9 | 10 | What did you expect to happen? 11 | 12 | --- 13 | 14 | ## Reproduction steps 15 | 16 | Steps to trigger the issue or paste minimal code. 17 | 18 | --- 19 | 20 | ## Environment 21 | 22 | - templUI version: 23 | - Go version: 24 | - Templ version: 25 | - Tailwind version: 26 | - Browser (if relevant): 27 | 28 | --- 29 | 30 | ## 🔍 Please include a minimal sample repo if possible 31 | 32 | A tiny repro repo or Gist helps a lot – thanks! 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature or improvement for templUI 4 | title: "[Feature] " 5 | labels: enhancement 6 | --- 7 | 8 | ## What would you like to see? 9 | 10 | Describe the feature or improvement you'd like to propose. 11 | 12 | --- 13 | 14 | ## Why is it useful? 15 | 16 | Explain how it helps you or improves templUI. 17 | 18 | --- 19 | 20 | ## Related components (if any) 21 | 22 | Mention any affected components, if applicable. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question or get help using templUI 4 | title: "[Question] " 5 | labels: question 6 | --- 7 | 8 | ## What's your question? 9 | 10 | Be as specific as possible. 11 | 12 | --- 13 | 14 | ## What have you tried? 15 | 16 | If you tried something that didn’t work, mention it here. 17 | 18 | --- 19 | 20 | ## Optional: Link to code / example 21 | 22 | If relevant, link to a code snippet or repo. 23 | -------------------------------------------------------------------------------- /.github/workflows/validate-html.yml: -------------------------------------------------------------------------------- 1 | name: Validate HTML 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | validate-html: 11 | name: 🧪 Build & Lint HTML from templUI Components 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: 🛎️ Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: 🧰 Set up Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: "1.21" 22 | 23 | - name: 📦 Install Go + Node deps 24 | run: | 25 | sudo apt update 26 | sudo apt install -y curl 27 | curl -fsSL https://bun.sh/install | bash 28 | echo "$HOME/.bun/bin" >> $GITHUB_PATH 29 | 30 | - name: 📦 Install Templ 31 | run: go install github.com/a-h/templ/cmd/templ@v0.3.865 32 | 33 | - name: 📦 Install Node dependencies 34 | run: | 35 | npm ci 36 | 37 | - name: ✅ Run `make validate-html` 38 | run: make validate-html 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*_templ.go 2 | **/*_templ.txt 3 | 4 | .DS_Store 5 | bin 6 | node_modules 7 | tmp 8 | .env 9 | 10 | out 11 | assets/css/output.css 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Go Docs", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "program": "${workspaceFolder}/cmd/docs", 10 | "args": ["serve", "--dir", "./pb_data"], 11 | "env": {}, 12 | "cwd": "${workspaceFolder}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build-Stage 2 | FROM golang:1.24 AS build 3 | WORKDIR /app 4 | 5 | # Copy the source code 6 | COPY . . 7 | 8 | # Install templ 9 | RUN go install github.com/a-h/templ/cmd/templ@latest 10 | 11 | # Generate templ files 12 | RUN templ generate 13 | 14 | # Install build dependencies 15 | RUN apt-get update && apt-get install -y curl wget && rm -rf /var/lib/apt/lists/* 16 | 17 | # Get the latest version from GitHub API and save it to version.txt 18 | RUN curl -s https://api.github.com/repos/axzilla/templui/releases/latest | grep tag_name | cut -d '"' -f 4 > version.txt || echo "unknown" > version.txt 19 | 20 | # Install Tailwind CSS standalone CLI 21 | RUN ARCH=$(uname -m) && \ 22 | if [ "$ARCH" = "x86_64" ]; then \ 23 | TAILWIND_URL="https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.3/tailwindcss-linux-x64"; \ 24 | elif [ "$ARCH" = "aarch64" ]; then \ 25 | TAILWIND_URL="https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.3/tailwindcss-linux-arm64"; \ 26 | else \ 27 | echo "Unsupported architecture: $ARCH"; exit 1; \ 28 | fi && \ 29 | wget -O tailwindcss "$TAILWIND_URL" && \ 30 | chmod +x tailwindcss 31 | 32 | # Generate Tailwind CSS output 33 | RUN ./tailwindcss -i ./assets/css/input.css -o ./assets/css/output.css --minify 34 | 35 | # Build the application as a static binary 36 | RUN CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/docs/main.go 37 | 38 | # Deploy-Stage 39 | FROM alpine:3.20.2 40 | WORKDIR /app 41 | 42 | # Install ca-certificates 43 | RUN apk add --no-cache ca-certificates 44 | 45 | # Set environment variable for runtime 46 | ENV GO_ENV=production 47 | 48 | # Copy the binary, version file, and CSS output 49 | COPY --from=build /app/main . 50 | COPY --from=build /app/version.txt . 51 | COPY --from=build /app/assets/css/output.css ./assets/css/output.css 52 | 53 | # Expose the port 54 | EXPOSE 8090 55 | 56 | # Command to run 57 | CMD ["./main"] 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Axel Adrian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import "embed" 4 | 5 | //go:embed css/* img/* js/* 6 | var Assets embed.FS 7 | -------------------------------------------------------------------------------- /assets/img/aspect_ratio_placeholder.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/aspect_ratio_placeholder.jpeg -------------------------------------------------------------------------------- /assets/img/card_placeholder.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/card_placeholder.jpeg -------------------------------------------------------------------------------- /assets/img/demo/carousel-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/demo/carousel-1.jpeg -------------------------------------------------------------------------------- /assets/img/demo/carousel-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/demo/carousel-2.jpeg -------------------------------------------------------------------------------- /assets/img/demo/carousel-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/demo/carousel-3.jpeg -------------------------------------------------------------------------------- /assets/img/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/readme.png -------------------------------------------------------------------------------- /assets/img/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axzilla/templui/85a39ea236948e44020e36ce40ab4aa4a611a7af/assets/img/social-preview.png -------------------------------------------------------------------------------- /assets/img/tailwindcss-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | go-app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | # Maps host port 8090 to container port 8090 of the Go app 10 | - "${PORT:-8090}:8090" 11 | environment: 12 | # Override SHIKI_URL to use internal Docker service name 13 | SHIKI_URL: http://shiki-service:3000/highlight 14 | # Set port explicitly in case the app needs to read it from ENV 15 | PORT: "8090" 16 | # GIN_MODE: "release" # Example for other ENV variables 17 | depends_on: 18 | - shiki-service # Ensures shiki-service starts first 19 | restart: unless-stopped 20 | networks: 21 | - templui-net 22 | # Resource limits 23 | deploy: 24 | resources: 25 | limits: 26 | cpus: "1.0" # Limit Go app to 1 CPU core 27 | memory: 512M # Limit Go app to 512 MB RAM 28 | # volumes: 29 | # Optional: Local directory for persistent data if needed 30 | # - ./data:/app/data 31 | 32 | shiki-service: 33 | build: 34 | context: ./shiki 35 | dockerfile: Dockerfile 36 | # No 'ports' section here -> port 3000 is only internally accessible 37 | restart: unless-stopped 38 | networks: 39 | - templui-net 40 | # Resource limits 41 | deploy: 42 | resources: 43 | limits: 44 | cpus: "3.0" # Limit Shiki service (all instances) to 3 CPU cores total 45 | memory: 1536M # Limit Shiki service (all instances) to 1.5 GB RAM total 46 | # Optional: Environment variables for the Shiki server if needed 47 | # environment: 48 | # NODE_ENV: production 49 | 50 | networks: 51 | templui-net: 52 | driver: bridge 53 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const htmlPlugin = require("@html-eslint/eslint-plugin"); 2 | const eslintHTMLParser = require("@html-eslint/parser"); 3 | const tailwind = require("eslint-plugin-html-tailwind"); 4 | 5 | module.exports = [ 6 | { 7 | files: ["out/**/*.html"], 8 | languageOptions: { 9 | parser: eslintHTMLParser, 10 | }, 11 | plugins: { 12 | "@html-eslint": htmlPlugin, 13 | "html-tailwind": tailwind, 14 | }, 15 | rules: { 16 | ...htmlPlugin.configs.recommended.rules, 17 | ...tailwind.configs.recommended.rules, 18 | "html-tailwind/classname-order": "off", 19 | "html-tailwind/no-style-attribute": "warn", 20 | "html-tailwind/no-contradicting-classnames": "warn", 21 | }, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/axzilla/templui 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/Oudwins/tailwind-merge-go v0.2.0 7 | github.com/a-h/templ v0.3.898 8 | github.com/joho/godotenv v1.5.1 9 | ) 10 | 11 | require ( 12 | github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect 13 | github.com/andybalholm/brotli v1.1.0 // indirect 14 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 15 | github.com/cli/browser v1.3.0 // indirect 16 | github.com/fatih/color v1.16.0 // indirect 17 | github.com/fsnotify/fsnotify v1.7.0 // indirect 18 | github.com/mattn/go-colorable v0.1.13 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/natefinch/atomic v1.0.1 // indirect 21 | golang.org/x/mod v0.24.0 // indirect 22 | golang.org/x/net v0.39.0 // indirect 23 | golang.org/x/sync v0.13.0 // indirect 24 | golang.org/x/sys v0.32.0 // indirect 25 | golang.org/x/tools v0.32.0 // indirect 26 | ) 27 | 28 | tool github.com/a-h/templ/cmd/templ 29 | -------------------------------------------------------------------------------- /internal/components/aspectratio/aspectratio.templ: -------------------------------------------------------------------------------- 1 | package aspectratio 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Ratio string 6 | 7 | const ( 8 | RatioAuto Ratio = "auto" 9 | RatioSquare Ratio = "square" 10 | RatioVideo Ratio = "video" 11 | RatioPortrait Ratio = "portrait" 12 | RatioWide Ratio = "wide" 13 | ) 14 | 15 | type Props struct { 16 | ID string 17 | Class string 18 | Attributes templ.Attributes 19 | Ratio Ratio 20 | } 21 | 22 | templ AspectRatio(props ...Props) { 23 | {{ var p Props }} 24 | if len(props) > 0 { 25 | {{ p = props[0] }} 26 | } 27 |
40 |
41 | { children... } 42 |
43 |
44 | } 45 | 46 | func ratioClass(ratio Ratio) string { 47 | switch ratio { 48 | case RatioSquare: 49 | return "aspect-square" 50 | case RatioVideo: 51 | return "aspect-video" 52 | case RatioPortrait: 53 | return "aspect-[3/4]" 54 | case RatioWide: 55 | return "aspect-[2/1]" 56 | case RatioAuto: 57 | return "aspect-auto" 58 | default: 59 | return "aspect-auto" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /internal/components/avatar/avatar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // IIFE 3 | function initAvatar(avatar) { 4 | const image = avatar.querySelector("[data-avatar-image]"); 5 | const fallback = avatar.querySelector("[data-avatar-fallback]"); 6 | 7 | if (image && fallback) { 8 | image.style.display = "none"; 9 | fallback.style.display = "none"; 10 | 11 | const showFallback = () => { 12 | image.style.display = "none"; 13 | fallback.style.display = ""; 14 | }; 15 | 16 | const showImage = () => { 17 | image.style.display = ""; 18 | fallback.style.display = "none"; 19 | }; 20 | 21 | if (image.complete) { 22 | image.naturalWidth > 0 && image.naturalHeight > 0 23 | ? showImage() 24 | : showFallback(); 25 | } else { 26 | image.addEventListener("load", showImage, { once: true }); 27 | image.addEventListener("error", showFallback, { once: true }); 28 | 29 | setTimeout(() => { 30 | if ( 31 | image.complete && 32 | !(image.naturalWidth > 0 && image.naturalHeight > 0) 33 | ) { 34 | showFallback(); 35 | } 36 | }, 50); 37 | } 38 | } else if (fallback) { 39 | fallback.style.display = ""; 40 | } else if (image) { 41 | image.style.display = ""; 42 | } 43 | } 44 | 45 | function initAllComponents(root = document) { 46 | if (root instanceof Element && root.matches("[data-avatar]")) { 47 | initAvatar(root); 48 | } 49 | 50 | for (const avatar of root.querySelectorAll("[data-avatar]")) { 51 | initAvatar(avatar); 52 | } 53 | } 54 | 55 | const handleHtmxSwap = (event) => { 56 | const target = event.detail.target || event.detail.elt; 57 | if (target instanceof Element) { 58 | requestAnimationFrame(() => initAllComponents(target)); 59 | } 60 | }; 61 | 62 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 63 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 64 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 65 | })(); // End of IIFE 66 | -------------------------------------------------------------------------------- /internal/components/avatar/avatar.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function l(t){let e=t.querySelector("[data-avatar-image]"),a=t.querySelector("[data-avatar-fallback]");if(e&&a){e.style.display="none",a.style.display="none";let n=()=>{e.style.display="none",a.style.display=""},o=()=>{e.style.display="",a.style.display="none"};e.complete?e.naturalWidth>0&&e.naturalHeight>0?o():n():(e.addEventListener("load",o,{once:!0}),e.addEventListener("error",n,{once:!0}),setTimeout(()=>{e.complete&&!(e.naturalWidth>0&&e.naturalHeight>0)&&n()},50))}else a?a.style.display="":e&&(e.style.display="")}function i(t=document){t instanceof Element&&t.matches("[data-avatar]")&&l(t);for(let e of t.querySelectorAll("[data-avatar]"))l(e)}let s=t=>{let e=t.detail.target||t.detail.elt;e instanceof Element&&requestAnimationFrame(()=>i(e))};document.addEventListener("DOMContentLoaded",()=>i()),document.body.addEventListener("htmx:afterSwap",s),document.body.addEventListener("htmx:oobAfterSwap",s)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/badge/badge.templ: -------------------------------------------------------------------------------- 1 | package badge 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Variant string 6 | 7 | const ( 8 | VariantDefault Variant = "default" 9 | VariantSecondary Variant = "secondary" 10 | VariantDestructive Variant = "destructive" 11 | VariantOutline Variant = "outline" 12 | ) 13 | 14 | type Props struct { 15 | ID string 16 | Class string 17 | Attributes templ.Attributes 18 | Variant Variant 19 | } 20 | 21 | templ Badge(props ...Props) { 22 | {{ var p Props }} 23 | if len(props) > 0 { 24 | {{ p = props[0] }} 25 | } 26 |
41 | { children... } 42 |
43 | } 44 | 45 | func (p Props) variantClasses() string { 46 | switch p.Variant { 47 | case VariantDestructive: 48 | return "border-transparent bg-destructive text-destructive-foreground" 49 | case VariantOutline: 50 | return "text-foreground border-border" 51 | case VariantSecondary: 52 | return "border-transparent bg-secondary text-secondary-foreground" 53 | default: 54 | return "border-transparent bg-primary text-primary-foreground" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/components/checkbox/checkbox.templ: -------------------------------------------------------------------------------- 1 | package checkbox 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/icon" 5 | "github.com/axzilla/templui/internal/utils" 6 | ) 7 | 8 | type Props struct { 9 | ID string 10 | Class string 11 | Attributes templ.Attributes 12 | Name string 13 | Value string 14 | Disabled bool 15 | Required bool 16 | Checked bool 17 | Icon templ.Component 18 | } 19 | 20 | templ Checkbox(props ...Props) { 21 | {{ var p Props }} 22 | if len(props) > 0 { 23 | {{ p = props[0] }} 24 | } 25 |
26 | 53 |
62 | if p.Icon != nil { 63 | @p.Icon 64 | } else { 65 | @icon.Check(icon.Props{Size: 12}) 66 | } 67 |
68 |
69 | } 70 | -------------------------------------------------------------------------------- /internal/components/code/code.templ: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/icon" 5 | "github.com/axzilla/templui/internal/utils" 6 | ) 7 | 8 | type Size string 9 | 10 | const ( 11 | SizeSm Size = "sm" 12 | SizeLg Size = "lg" 13 | SizeFull Size = "full" 14 | ) 15 | 16 | type Props struct { 17 | ID string 18 | Class string 19 | Attrs templ.Attributes 20 | Language string 21 | ShowCopyButton bool 22 | Size Size 23 | CodeClass string 24 | } 25 | 26 | templ Code(props ...Props) { 27 | 28 | {{ var p Props }} 29 | if len(props) > 0 { 30 | {{ p = props[0] }} 31 | } 32 | if p.ID == "" { 33 | {{ p.ID = "code-" + utils.RandomID() }} 34 | } 35 |
41 |
42 | 			
56 | 				{ children... }
57 | 			
58 | 		
59 | if p.ShowCopyButton { 60 | 72 | } 73 |
74 | } 75 | -------------------------------------------------------------------------------- /internal/components/dropdown/dropdown.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // IIFE 3 | function handleDropdownItemClick(event) { 4 | const item = event.currentTarget; 5 | const popoverContent = item.closest("[data-popover-id]"); 6 | if (popoverContent) { 7 | const popoverId = popoverContent.dataset.popoverId; 8 | if (window.closePopover) { 9 | window.closePopover(popoverId, true); 10 | } else { 11 | console.warn("popover.Script's closePopover function not found."); 12 | document.body.click(); // Fallback 13 | } 14 | } 15 | } 16 | 17 | function initAllComponents(root = document) { 18 | // Select items with 'data-dropdown-item' but not 'data-dropdown-submenu-trigger' 19 | const items = root.querySelectorAll( 20 | "[data-dropdown-item]:not([data-dropdown-submenu-trigger])" 21 | ); 22 | items.forEach((item) => { 23 | item.removeEventListener("click", handleDropdownItemClick); 24 | item.addEventListener("click", handleDropdownItemClick); 25 | }); 26 | } 27 | 28 | const handleHtmxSwap = (event) => { 29 | const target = event.detail.target || event.detail.elt; 30 | if (target instanceof Element) { 31 | requestAnimationFrame(() => initAllComponents(target)); 32 | } 33 | }; 34 | 35 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 36 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 37 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 38 | })(); // End of IIFE 39 | -------------------------------------------------------------------------------- /internal/components/dropdown/dropdown.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function n(e){let t=e.currentTarget.closest("[data-popover-id]");if(t){let c=t.dataset.popoverId;window.closePopover?window.closePopover(c,!0):(console.warn("popover.Script's closePopover function not found."),document.body.click())}}function d(e=document){e.querySelectorAll("[data-dropdown-item]:not([data-dropdown-submenu-trigger])").forEach(t=>{t.removeEventListener("click",n),t.addEventListener("click",n)})}let r=e=>{let o=e.detail.target||e.detail.elt;o instanceof Element&&requestAnimationFrame(()=>d(o))};document.addEventListener("DOMContentLoaded",()=>d()),document.body.addEventListener("htmx:afterSwap",r),document.body.addEventListener("htmx:oobAfterSwap",r)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/embed.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import "embed" 4 | 5 | //go:embed **/*.templ **/*.go 6 | var TemplFiles embed.FS 7 | -------------------------------------------------------------------------------- /internal/components/input/input.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function initPasswordToggle(button) { 3 | if (button.hasAttribute("data-password-initialized")) { 4 | return; 5 | } 6 | 7 | button.setAttribute("data-password-initialized", "true"); 8 | 9 | button.addEventListener("click", function (event) { 10 | const inputId = button.getAttribute("data-toggle-password"); 11 | const input = document.getElementById(inputId); 12 | if (input) { 13 | const iconOpen = button.querySelector(".icon-open"); 14 | const iconClosed = button.querySelector(".icon-closed"); 15 | 16 | if (input.type === "password") { 17 | input.type = "text"; 18 | iconOpen.classList.add("hidden"); 19 | iconClosed.classList.remove("hidden"); 20 | } else { 21 | input.type = "password"; 22 | iconOpen.classList.remove("hidden"); 23 | iconClosed.classList.add("hidden"); 24 | } 25 | } 26 | }); 27 | } 28 | 29 | function initAllComponents(root = document) { 30 | const buttons = root.querySelectorAll( 31 | "[data-toggle-password]:not([data-password-initialized])" 32 | ); 33 | buttons.forEach((button) => { 34 | initPasswordToggle(button); 35 | }); 36 | } 37 | 38 | const handleHtmxSwap = (event) => { 39 | const target = event.detail.target || event.detail.elt; 40 | if (target instanceof Element) { 41 | requestAnimationFrame(() => initAllComponents(target)); 42 | } 43 | }; 44 | 45 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 46 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 47 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 48 | })(); 49 | -------------------------------------------------------------------------------- /internal/components/input/input.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function r(t){t.hasAttribute("data-password-initialized")||(t.setAttribute("data-password-initialized","true"),t.addEventListener("click",function(e){let d=t.getAttribute("data-toggle-password"),n=document.getElementById(d);if(n){let o=t.querySelector(".icon-open"),a=t.querySelector(".icon-closed");n.type==="password"?(n.type="text",o.classList.add("hidden"),a.classList.remove("hidden")):(n.type="password",o.classList.remove("hidden"),a.classList.add("hidden"))}}))}function i(t=document){t.querySelectorAll("[data-toggle-password]:not([data-password-initialized])").forEach(d=>{r(d)})}let s=t=>{let e=t.detail.target||t.detail.elt;e instanceof Element&&requestAnimationFrame(()=>i(e))};document.addEventListener("DOMContentLoaded",()=>i()),document.body.addEventListener("htmx:afterSwap",s),document.body.addEventListener("htmx:oobAfterSwap",s)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/label/label.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function a(t){if(!t.hasAttribute("for")||!t.hasAttribute("data-disabled-style"))return;let e=t.getAttribute("for"),n=e?document.getElementById(e):null,d=t.getAttribute("data-disabled-style");if(!d)return;let o=d.split(" ").filter(Boolean);function r(){n&&n.disabled?t.classList.add(...o):t.classList.remove(...o)}n&&new MutationObserver(u=>{for(let l of u)l.type==="attributes"&&l.attributeName==="disabled"&&r()}).observe(n,{attributes:!0,attributeFilter:["disabled"]}),r()}function s(t=document){t instanceof Element&&t.matches("label[for][data-disabled-style]")&&a(t);for(let e of t.querySelectorAll("label[for][data-disabled-style]"))a(e)}let i=t=>{let e=t.detail.target||t.detail.elt;e instanceof Element&&requestAnimationFrame(()=>s(e))};document.addEventListener("DOMContentLoaded",()=>s()),document.body.addEventListener("htmx:afterSwap",i),document.body.addEventListener("htmx:oobAfterSwap",i)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/label/label.templ: -------------------------------------------------------------------------------- 1 | package label 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Props struct { 6 | ID string 7 | Class string 8 | Attributes templ.Attributes 9 | For string 10 | Error string 11 | } 12 | 13 | templ Label(props ...Props) { 14 | {{ var p Props }} 15 | if len(props) > 0 { 16 | {{ p = props[0] }} 17 | } 18 | 37 | } 38 | -------------------------------------------------------------------------------- /internal/components/main.js: -------------------------------------------------------------------------------- 1 | import "./avatar/avatar.js"; 2 | import "./calendar/calendar.js"; 3 | import "./carousel/carousel.js"; 4 | import "./chart/chart.js"; 5 | import "./code/code.js"; 6 | import "./datepicker/datepicker.js"; 7 | import "./drawer/drawer.js"; 8 | import "./dropdown/dropdown.js"; 9 | import "./input/input.js"; 10 | import "./inputotp/inputotp.js"; 11 | import "./label/label.js"; 12 | import "./modal/modal.js"; 13 | import "./popover/popover.js"; 14 | import "./progress/progress.js"; 15 | import "./rating/rating.js"; 16 | import "./selectbox/selectbox.js"; 17 | import "./slider/slider.js"; 18 | import "./tabs/tabs.js"; 19 | import "./textarea/textarea.js"; 20 | import "./toast/toast.js"; 21 | -------------------------------------------------------------------------------- /internal/components/progress/progress.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function updateProgressWidth(progressBar) { 3 | if (!progressBar) return; 4 | 5 | const indicator = progressBar.querySelector("[data-progress-indicator]"); 6 | if (!indicator) return; 7 | 8 | const value = parseFloat(progressBar.getAttribute("aria-valuenow") || "0"); 9 | let max = parseFloat(progressBar.getAttribute("aria-valuemax") || "100"); 10 | if (max <= 0) max = 100; 11 | 12 | let percentage = 0; 13 | if (max > 0) { 14 | percentage = (Math.max(0, Math.min(value, max)) / max) * 100; 15 | } 16 | 17 | indicator.style.width = percentage + "%"; 18 | } 19 | 20 | function initAllComponents(root = document) { 21 | if (root instanceof Element && root.matches('[role="progressbar"]')) { 22 | updateProgressWidth(root); 23 | } 24 | if (root && typeof root.querySelectorAll === "function") { 25 | for (const progressBar of root.querySelectorAll('[role="progressbar"]')) { 26 | updateProgressWidth(progressBar); 27 | } 28 | } 29 | } 30 | 31 | const handleHtmxSwap = (event) => { 32 | const target = event.detail.target || event.detail.elt; 33 | if (target instanceof Element) { 34 | requestAnimationFrame(() => initAllComponents(target)); 35 | } 36 | }; 37 | 38 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 39 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 40 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 41 | })(); 42 | -------------------------------------------------------------------------------- /internal/components/progress/progress.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function a(e){if(!e)return;let t=e.querySelector("[data-progress-indicator]");if(!t)return;let l=parseFloat(e.getAttribute("aria-valuenow")||"0"),n=parseFloat(e.getAttribute("aria-valuemax")||"100");n<=0&&(n=100);let o=0;n>0&&(o=Math.max(0,Math.min(l,n))/n*100),t.style.width=o+"%"}function i(e=document){if(e instanceof Element&&e.matches('[role="progressbar"]')&&a(e),e&&typeof e.querySelectorAll=="function")for(let t of e.querySelectorAll('[role="progressbar"]'))a(t)}let r=e=>{let t=e.detail.target||e.detail.elt;t instanceof Element&&requestAnimationFrame(()=>i(t))};document.addEventListener("DOMContentLoaded",()=>i()),document.body.addEventListener("htmx:afterSwap",r),document.body.addEventListener("htmx:oobAfterSwap",r)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/radio/radio.templ: -------------------------------------------------------------------------------- 1 | package radio 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Props struct { 6 | ID string 7 | Class string 8 | Attributes templ.Attributes 9 | Name string 10 | Value string 11 | Disabled bool 12 | Required bool 13 | Checked bool 14 | } 15 | 16 | templ Radio(props ...Props) { 17 | {{ var p Props }} 18 | if len(props) > 0 { 19 | {{ p = props[0] }} 20 | } 21 | 53 | } 54 | -------------------------------------------------------------------------------- /internal/components/skeleton/skeleton.templ: -------------------------------------------------------------------------------- 1 | package skeleton 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Props struct { 6 | ID string 7 | Class string 8 | Attributes templ.Attributes 9 | } 10 | 11 | templ Skeleton(props ...Props) { 12 | {{ var p Props }} 13 | if len(props) > 0 { 14 | {{ p = props[0] }} 15 | } 16 |
28 | } 29 | -------------------------------------------------------------------------------- /internal/components/slider/slider.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // IIFE 3 | function initSlider(sliderInput) { 4 | if (sliderInput.hasAttribute("data-initialized")) return; 5 | 6 | sliderInput.setAttribute("data-initialized", "true"); 7 | 8 | const sliderId = sliderInput.id; 9 | if (!sliderId) return; 10 | 11 | const valueElements = document.querySelectorAll( 12 | `[data-slider-value][data-slider-value-for="${sliderId}"]` 13 | ); 14 | 15 | function updateValues() { 16 | valueElements.forEach((el) => { 17 | el.textContent = sliderInput.value; 18 | }); 19 | } 20 | 21 | updateValues(); 22 | sliderInput.addEventListener("input", updateValues); 23 | } 24 | 25 | function initAllComponents(root = document) { 26 | if ( 27 | root instanceof Element && 28 | root.matches('input[type="range"][data-slider-input]') 29 | ) { 30 | initSlider(root); 31 | } 32 | for (const slider of root.querySelectorAll( 33 | 'input[type="range"][data-slider-input]:not([data-initialized])' 34 | )) { 35 | initSlider(slider); 36 | } 37 | } 38 | 39 | const handleHtmxSwap = (event) => { 40 | const target = event.detail.target || event.detail.elt; 41 | if (target instanceof Element) { 42 | requestAnimationFrame(() => initAllComponents(target)); 43 | } 44 | }; 45 | 46 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 47 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 48 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 49 | })(); // End of IIFE 50 | -------------------------------------------------------------------------------- /internal/components/slider/slider.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function n(t){if(t.hasAttribute("data-initialized"))return;t.setAttribute("data-initialized","true");let e=t.id;if(!e)return;let o=document.querySelectorAll(`[data-slider-value][data-slider-value-for="${e}"]`);function d(){o.forEach(l=>{l.textContent=t.value})}d(),t.addEventListener("input",d)}function a(t=document){t instanceof Element&&t.matches('input[type="range"][data-slider-input]')&&n(t);for(let e of t.querySelectorAll('input[type="range"][data-slider-input]:not([data-initialized])'))n(e)}let i=t=>{let e=t.detail.target||t.detail.elt;e instanceof Element&&requestAnimationFrame(()=>a(e))};document.addEventListener("DOMContentLoaded",()=>a()),document.body.addEventListener("htmx:afterSwap",i),document.body.addEventListener("htmx:oobAfterSwap",i)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/spinner/spinner.templ: -------------------------------------------------------------------------------- 1 | package spinner 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Size string 6 | 7 | const ( 8 | SizeSm Size = "sm" 9 | SizeMd Size = "md" 10 | SizeLg Size = "lg" 11 | ) 12 | 13 | type Props struct { 14 | ID string 15 | Class string 16 | Attributes templ.Attributes 17 | Size Size 18 | Color string 19 | } 20 | 21 | templ Spinner(props ...Props) { 22 | {{ var p Props }} 23 | if len(props) > 0 { 24 | {{ p = props[0] }} 25 | } 26 |
40 |
59 |
60 | } 61 | 62 | func sizeClass(size Size) string { 63 | switch size { 64 | case SizeSm: 65 | return "w-6 h-6" 66 | case SizeLg: 67 | return "w-12 h-12" 68 | default: 69 | return "w-8 h-8" 70 | } 71 | } 72 | 73 | func borderSizeClass(size Size) string { 74 | switch size { 75 | case SizeSm: 76 | return "border-[3px]" 77 | case SizeLg: 78 | return "border-[5px]" 79 | default: 80 | return "border-4" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /internal/components/tabs/tabs.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function o(t){if(t.hasAttribute("data-initialized"))return;t.setAttribute("data-initialized","true");let s=t.dataset.tabsId;if(!s)return;let n=Array.from(t.querySelectorAll(`[data-tabs-trigger][data-tabs-id="${s}"]`)),b=Array.from(t.querySelectorAll(`[data-tabs-content][data-tabs-id="${s}"]`)),d=t.querySelector(`[data-tabs-marker][data-tabs-id="${s}"]`);function g(e){!d||!e||(d.style.width=e.offsetWidth+"px",d.style.height=e.offsetHeight+"px",d.style.left=e.offsetLeft+"px")}function c(e){let u=null;for(let a of n){let i=a.dataset.tabsValue===e;a.dataset.state=i?"active":"inactive",a.classList.toggle("text-foreground",i),a.classList.toggle("bg-background",i),a.classList.toggle("shadow-xs",i),i&&(u=a)}for(let a of b){let i=a.dataset.tabsValue===e;a.dataset.state=i?"active":"inactive",a.classList.toggle("hidden",!i)}g(u)}let f=n.find(e=>e.dataset.state==="active")||n[0];f&&c(f.dataset.tabsValue);for(let e of n)e.addEventListener("click",()=>{c(e.dataset.tabsValue)})}function r(t=document){t instanceof Element&&t.matches("[data-tabs]")&&o(t);for(let s of t.querySelectorAll("[data-tabs]:not([data-initialized])"))o(s)}let l=t=>{let s=t.detail.target||t.detail.elt;s instanceof Element&&requestAnimationFrame(()=>r(s))};document.addEventListener("DOMContentLoaded",()=>r()),document.body.addEventListener("htmx:afterSwap",l),document.body.addEventListener("htmx:oobAfterSwap",l)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/textarea/textarea.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // IIFE 3 | function initTextarea(textarea) { 4 | if (textarea.hasAttribute("data-initialized")) return; 5 | 6 | textarea.setAttribute("data-initialized", "true"); 7 | 8 | const autoResize = textarea.dataset.autoResize === "true"; 9 | if (!autoResize) return; 10 | 11 | const computedStyle = window.getComputedStyle(textarea); 12 | const initialMinHeight = computedStyle.minHeight; 13 | 14 | function resize() { 15 | textarea.style.height = initialMinHeight; 16 | textarea.style.height = `${textarea.scrollHeight}px`; 17 | } 18 | 19 | resize(); 20 | textarea.addEventListener("input", resize); 21 | } 22 | 23 | function initAllComponents(root = document) { 24 | if (root instanceof Element && root.matches("textarea[data-textarea]")) { 25 | initTextarea(root); 26 | } 27 | for (const textarea of root.querySelectorAll( 28 | "textarea[data-textarea]:not([data-initialized])" 29 | )) { 30 | initTextarea(textarea); 31 | } 32 | } 33 | 34 | const handleHtmxSwap = (event) => { 35 | const target = event.detail.target || event.detail.elt; 36 | if (target instanceof Element) { 37 | requestAnimationFrame(() => initAllComponents(target)); 38 | } 39 | }; 40 | 41 | document.addEventListener("DOMContentLoaded", () => initAllComponents()); 42 | document.body.addEventListener("htmx:afterSwap", handleHtmxSwap); 43 | document.body.addEventListener("htmx:oobAfterSwap", handleHtmxSwap); 44 | })(); // End of IIFE 45 | -------------------------------------------------------------------------------- /internal/components/textarea/textarea.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){function i(t){if(t.hasAttribute("data-initialized")||(t.setAttribute("data-initialized","true"),!(t.dataset.autoResize==="true")))return;let o=window.getComputedStyle(t).minHeight;function d(){t.style.height=o,t.style.height=`${t.scrollHeight}px`}d(),t.addEventListener("input",d)}function n(t=document){t instanceof Element&&t.matches("textarea[data-textarea]")&&i(t);for(let e of t.querySelectorAll("textarea[data-textarea]:not([data-initialized])"))i(e)}let a=t=>{let e=t.detail.target||t.detail.elt;e instanceof Element&&requestAnimationFrame(()=>n(e))};document.addEventListener("DOMContentLoaded",()=>n()),document.body.addEventListener("htmx:afterSwap",a),document.body.addEventListener("htmx:oobAfterSwap",a)})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/textarea/textarea.templ: -------------------------------------------------------------------------------- 1 | package textarea 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/utils" 5 | "strconv" 6 | ) 7 | 8 | type Props struct { 9 | ID string 10 | Class string 11 | Attributes templ.Attributes 12 | Name string 13 | Value string 14 | Placeholder string 15 | Rows int 16 | AutoResize bool 17 | Disabled bool 18 | Required bool 19 | } 20 | 21 | templ Textarea(props ...Props) { 22 | {{ var p Props }} 23 | if len(props) > 0 { 24 | {{ p = props[0] }} 25 | } 26 | if p.ID == "" { 27 | {{ p.ID = utils.RandomID() }} 28 | } 29 | 62 | } 63 | -------------------------------------------------------------------------------- /internal/components/toast/toast.min.js: -------------------------------------------------------------------------------- 1 | (()=>{(function(){if(typeof window.toastHandler>"u"){let a=function(t){if(window.toasts.has(t))return;let s=parseInt(t.dataset.duration||"0"),i=t.querySelector("[data-toast-progress]"),d=t.querySelector("[data-toast-dismiss]"),e={timer:null,remaining:s,startTime:Date.now(),progress:i,paused:!1};window.toasts.set(t,e);function u(){clearTimeout(e.timer),t.classList.remove("toast-enter-active"),t.classList.add("toast-leave-active"),t.addEventListener("transitionend",()=>{t.remove(),window.toasts.delete(t)},{once:!0})}function c(n){n<=0||(clearTimeout(e.timer),e.startTime=Date.now(),e.remaining=n,e.paused=!1,e.timer=setTimeout(u,n),e.progress&&(e.progress.style.transition=`width ${n}ms linear`,e.progress.offsetWidth,e.progress.style.width="0%"))}function m(){if(!(e.paused||e.remaining<=0)&&(clearTimeout(e.timer),e.remaining-=Date.now()-e.startTime,e.paused=!0,e.progress)){let n=window.getComputedStyle(e.progress).width;e.progress.style.transition="none",e.progress.style.width=n}}function l(){!e.paused||e.remaining<=0||c(e.remaining)}s>0&&(t.addEventListener("mouseenter",m),t.addEventListener("mouseleave",l)),d&&d.addEventListener("click",u),setTimeout(()=>{t.classList.add("toast-enter-active"),e.progress&&(e.progress.style.width="100%"),c(s)},50)},r=function(t=document){let s=[];t instanceof Element&&t.matches("[data-toast]")&&(window.toasts.has(t)||s.push(t)),t&&typeof t.querySelectorAll=="function"&&t.querySelectorAll("[data-toast]").forEach(i=>{window.toasts.has(i)||s.push(i)}),s.forEach(a)};var f=a,w=r;window.toastHandler=!0,window.toasts=new Map;let o=t=>{let s=t.detail.target||t.detail.elt;s instanceof Element&&requestAnimationFrame(()=>r(s))};document.addEventListener("DOMContentLoaded",()=>r()),document.body.addEventListener("htmx:afterSwap",o),document.body.addEventListener("htmx:oobAfterSwap",o)}})();})(); 2 | -------------------------------------------------------------------------------- /internal/components/toggle/toggle.templ: -------------------------------------------------------------------------------- 1 | package toggle 2 | 3 | import "github.com/axzilla/templui/internal/utils" 4 | 5 | type Props struct { 6 | ID string 7 | Class string 8 | Attributes templ.Attributes 9 | Name string 10 | Disabled bool 11 | Checked bool 12 | } 13 | 14 | templ Toggle(props ...Props) { 15 | {{ var p Props }} 16 | if len(props) > 0 { 17 | {{ p = props[0] }} 18 | } 19 | if p.ID == "" { 20 | {{ p.ID = utils.RandomID() }} 21 | } 22 | 62 | } 63 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | ) 10 | 11 | type Config struct { 12 | GoEnv string 13 | } 14 | 15 | var AppConfig *Config 16 | 17 | func LoadConfig() { 18 | err := godotenv.Load() 19 | if err != nil { 20 | fmt.Println("Error loading .env file") 21 | } else { 22 | log.Println(".env file initialized.") 23 | } 24 | 25 | AppConfig = &Config{ 26 | GoEnv: os.Getenv("GO_ENV"), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/ctxkeys/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package ctxkeys 2 | 3 | type contextKey string 4 | 5 | const ( 6 | URLPathValue = contextKey("url_path_value") 7 | Version = contextKey("version") 8 | ) 9 | -------------------------------------------------------------------------------- /internal/ui/icons/github.templ: -------------------------------------------------------------------------------- 1 | package wrappedicon 2 | 3 | import "strconv" 4 | 5 | templ GitHub(size int) { 6 | 13 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /internal/ui/icons/js.templ: -------------------------------------------------------------------------------- 1 | package wrappedicon 2 | 3 | import "strconv" 4 | 5 | templ JS(size int) { 6 | 17 | 18 | 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /internal/ui/icons/tailwind.templ: -------------------------------------------------------------------------------- 1 | package wrappedicon 2 | 3 | import "strconv" 4 | 5 | templ Tailwind(size int) { 6 | 14 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/icons/x.templ: -------------------------------------------------------------------------------- 1 | package wrappedicon 2 | 3 | import "strconv" 4 | 5 | templ X(size int) { 6 | 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/layouts/docs.templ: -------------------------------------------------------------------------------- 1 | package layouts 2 | 3 | import "github.com/axzilla/templui/internal/ui/modules" 4 | 5 | templ DocsLayout(title, description string, tocItems []modules.TableOfContentsItem) { 6 | @BaseLayout(title, description) { 7 | @modules.TableOfContentsScript() 8 |
9 | @modules.AnnouncementBar() 10 | @modules.Navbar() 11 |
12 | 17 |
18 |
19 |
20 | { children... } 21 |
22 |
23 | @modules.Footer() 24 |
25 | 32 |
33 |
34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/ui/modules/announcement_bar.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | templ AnnouncementBar() { 4 |
5 |
6 | 10 | 11 | 12 | 13 | 14 | templUI Pro coming soon   15 | 16 |
17 |

18 | 19 | 50% OFF 20 | Join Waitlist 21 | 22 | 23 | 24 | 25 | 26 |

27 |
28 | } 29 | -------------------------------------------------------------------------------- /internal/ui/modules/codesnippet_embed.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import "embed" 4 | 5 | templ CodeSnippetFromEmbedded(filename, language string, embed embed.FS) { 6 | if content, err := embed.ReadFile(filename); err != nil { 7 |
Error reading file: { filename }: { err.Error() }
8 | } else { 9 | @Code(CodeProps{ 10 | Language: "templ", 11 | ShowCopyButton: true, 12 | CodeContent: string(content), 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/modules/component_usage.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | type ComponentUsageProps struct { 4 | ComponentName string 5 | // HasJS bool 6 | JSFiles []string 7 | } 8 | 9 | templ ComponentUsage(props ComponentUsageProps) { 10 | if len(props.JSFiles) == 0 { 11 | @Code(CodeProps{ 12 | CodeContent: "templui add " + props.ComponentName, 13 | ShowCopyButton: true, 14 | }) 15 | } else { 16 |
    17 |
  1. 18 |

    Install the component

    19 | @Code(CodeProps{ 20 | CodeContent: "templui add " + props.ComponentName, 21 | ShowCopyButton: true, 22 | }) 23 |
  2. 24 |
  3. 25 |

    Add the JavaScript to your layout

    26 | {{ 27 | var files string 28 | for _, jsFile := range props.JSFiles { 29 | files += "@" + jsFile + ".Script()\n" 30 | } 31 | }} 32 | @Code(CodeProps{ 33 | CodeContent: files, 34 | ShowCopyButton: true, 35 | }) 36 |

    Call this template in your base layout file (e.g., in the <head> section).

    37 |
  4. 38 |
39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/ui/modules/container_wrapper.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | type ContainerWrapperProps struct { 4 | Title string 5 | Description string 6 | ID string 7 | } 8 | 9 | templ ContainerWrapper(p ContainerWrapperProps) { 10 |
11 |
12 |

{ p.Title }

13 | if p.Description != "" { 14 |

{ p.Description }

15 | } 16 |
17 |
18 | { children... } 19 |
20 |
21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/modules/example_wrapper.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/tabs" 5 | "github.com/axzilla/templui/internal/ui/showcase" 6 | ) 7 | 8 | type showcaseWrapperProps struct { 9 | Content templ.Component 10 | } 11 | 12 | templ showcaseWrapper(p showcaseWrapperProps) { 13 |
14 | @p.Content 15 |
16 | } 17 | 18 | type ExampleWrapperProps struct { 19 | SectionName string 20 | ShowcaseFile templ.Component 21 | PreviewCodeFile string 22 | ID string // For #id in URL Link 23 | } 24 | 25 | templ ExampleWrapper(p ExampleWrapperProps) { 26 |
31 |

{ p.SectionName }

32 | @tabs.Tabs(tabs.Props{ 33 | ID: "example-" + p.ID, 34 | }) { 35 | @tabs.List(tabs.ListProps{ 36 | Class: "md:w-1/2", 37 | }) { 38 | @tabs.Trigger(tabs.TriggerProps{ 39 | Value: "preview", 40 | IsActive: true, 41 | }) { 42 | Preview 43 | } 44 | @tabs.Trigger(tabs.TriggerProps{ 45 | Value: "code", 46 | }) { 47 | Code 48 | } 49 | } 50 |
51 | @tabs.Content(tabs.ContentProps{ 52 | Value: "preview", 53 | IsActive: true, 54 | }) { 55 | @showcaseWrapper(showcaseWrapperProps{ 56 | Content: p.ShowcaseFile, 57 | }) 58 | } 59 | @tabs.Content(tabs.ContentProps{ 60 | Value: "code", 61 | }) { 62 | @CodeSnippetFromEmbedded(p.PreviewCodeFile, "go", showcase.TemplFiles) 63 | } 64 |
65 | } 66 |
67 | } 68 | -------------------------------------------------------------------------------- /internal/ui/modules/footer.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | "time" 7 | ) 8 | 9 | templ Footer() { 10 | 20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/modules/seo.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | // SEO is a simple module for all important SEO tags 4 | templ SEO(title, description, path string) { 5 | {{ title = title + " | templUI" }} 6 | { title } 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/modules/sidebar.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import "github.com/axzilla/templui/internal/shared" 4 | import "github.com/axzilla/templui/internal/ctxkeys" 5 | 6 | templ Sidebar() { 7 | 35 | } 36 | -------------------------------------------------------------------------------- /internal/ui/modules/themeswitcher.templ: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ themeSwitcherHandler() { 9 | {{ handle := templ.NewOnceHandle() }} 10 | @handle.Once() { 11 | 23 | } 24 | } 25 | 26 | type ThemeSwitcherProps struct { 27 | Class string 28 | } 29 | 30 | templ ThemeSwitcher(props ...ThemeSwitcherProps) { 31 | {{ var p ThemeSwitcherProps }} 32 | if len(props) > 0 { 33 | {{ p = props[0] }} 34 | } 35 | @themeSwitcherHandler() 36 | @button.Button(button.Props{ 37 | Size: button.SizeIcon, 38 | Variant: button.VariantGhost, 39 | Class: p.Class, 40 | Attributes: templ.Attributes{ 41 | "@click": "toggleTheme", 42 | }, 43 | }) { 44 | @icon.SunMedium() 45 | } 46 | } 47 | 48 | templ DynamicThemeIcon() { 49 |
50 | 51 | @LightIcon() 52 | 53 | 54 | @DarkIcon() 55 | 56 |
57 | } 58 | 59 | templ DarkIcon() { 60 | @icon.Moon() 61 | } 62 | 63 | templ LightIcon() { 64 | @icon.SunMedium() 65 | } 66 | -------------------------------------------------------------------------------- /internal/ui/pages/accordion.templ: -------------------------------------------------------------------------------- 1 | 2 | package pages 3 | 4 | import ( 5 | "github.com/axzilla/templui/internal/ui/layouts" 6 | "github.com/axzilla/templui/internal/ui/modules" 7 | "github.com/axzilla/templui/internal/ui/showcase" 8 | ) 9 | 10 | templ Accordion() { 11 | @layouts.DocsLayout( 12 | "Accordion", 13 | "Vertically stacked interactive sections to organize content.", 14 | []modules.TableOfContentsItem{ 15 | { 16 | ID: "installation", 17 | Text: "Installation", 18 | }, 19 | }, 20 | ) { 21 | @modules.PageWrapper(modules.PageWrapperProps{ 22 | Name: "Accordion", 23 | Description: templ.Raw("Vertically stacked interactive sections to organize content."), 24 | Tailwind: true, 25 | Breadcrumbs: modules.Breadcrumbs{ 26 | Items: []modules.BreadcrumbItem{ 27 | { 28 | Text: "Docs", 29 | Path: "/docs", 30 | }, 31 | { 32 | Text: "Accordion", 33 | }, 34 | }, 35 | }, 36 | }) { 37 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 38 | ShowcaseFile: showcase.AccordionDefault(), 39 | PreviewCodeFile: "accordion_default.templ", 40 | }) 41 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 42 | Title: "Installation", 43 | ID: "installation", 44 | }) { 45 | @modules.ComponentUsage(modules.ComponentUsageProps{ 46 | ComponentName: "accordion", 47 | }) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/ui/pages/alert.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Alert() { 10 | @layouts.DocsLayout( 11 | "Alert", 12 | "Status message that displays contextual feedback or notifications.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | { 19 | ID: "examples", 20 | Text: "Examples", 21 | Children: []modules.TableOfContentsItem{ 22 | { 23 | ID: "destructive", 24 | Text: "Destructive", 25 | }, 26 | }, 27 | }, 28 | }, 29 | ) { 30 | @modules.PageWrapper(modules.PageWrapperProps{ 31 | Name: "Alert", 32 | Description: templ.Raw("Status message that displays contextual feedback or notifications."), 33 | Tailwind: true, 34 | Breadcrumbs: modules.Breadcrumbs{ 35 | Items: []modules.BreadcrumbItem{ 36 | { 37 | Text: "Docs", 38 | Path: "/docs", 39 | }, 40 | { 41 | Text: "Alert", 42 | }, 43 | }, 44 | }, 45 | }) { 46 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 47 | ShowcaseFile: showcase.AlertDefault(), 48 | PreviewCodeFile: "alert_default.templ", 49 | }) 50 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 51 | Title: "Installation", 52 | ID: "installation", 53 | }) { 54 | @modules.ComponentUsage(modules.ComponentUsageProps{ 55 | ComponentName: "alert", 56 | }) 57 | } 58 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 59 | Title: "Examples", 60 | ID: "examples", 61 | }) { 62 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 63 | SectionName: "Destructive", 64 | ShowcaseFile: showcase.AlertDestructive(), 65 | PreviewCodeFile: "alert_destructive.templ", 66 | ID: "destructive", 67 | }) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/ui/pages/aspect_ratio.templ: -------------------------------------------------------------------------------- 1 | 2 | package pages 3 | 4 | import "github.com/axzilla/templui/internal/ui/layouts" 5 | import "github.com/axzilla/templui/internal/ui/modules" 6 | import "github.com/axzilla/templui/internal/ui/showcase" 7 | 8 | templ AspectRatio() { 9 | @layouts.DocsLayout( 10 | "Aspect Ratio", 11 | "A component for maintaining consistent width-to-height ratios across different screen sizes.", 12 | []modules.TableOfContentsItem{ 13 | { 14 | ID: "installation", 15 | Text: "Installation", 16 | }, 17 | }, 18 | ) { 19 | @modules.PageWrapper(modules.PageWrapperProps{ 20 | Name: "Aspect Ratio", 21 | Description: templ.Raw("A component for maintaining consistent width-to-height ratios across different screen sizes."), 22 | Tailwind: true, 23 | Breadcrumbs: modules.Breadcrumbs{ 24 | Items: []modules.BreadcrumbItem{ 25 | { 26 | Text: "Docs", 27 | Path: "/docs", 28 | }, 29 | { 30 | Text: "Aspect Ratio", 31 | }, 32 | }, 33 | }, 34 | }) { 35 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 36 | ShowcaseFile: showcase.AspectRatioDefault(), 37 | PreviewCodeFile: "aspect_ratio_default.templ", 38 | }) 39 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 40 | Title: "Installation", 41 | ID: "installation", 42 | }) { 43 | @modules.ComponentUsage(modules.ComponentUsageProps{ 44 | ComponentName: "aspectratio", 45 | }) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/ui/pages/calendar.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Calendar() { 10 | @layouts.DocsLayout( 11 | "Calendar", 12 | "A date field component that allows users to enter and edit date.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Calendar", 22 | Description: templ.Raw("A date field component that allows users to enter and edit date."), 23 | Tailwind: true, 24 | VanillaJS: true, 25 | Breadcrumbs: modules.Breadcrumbs{ 26 | Items: []modules.BreadcrumbItem{ 27 | { 28 | Text: "Docs", 29 | Path: "/docs", 30 | }, 31 | { 32 | Text: "Calendar", 33 | }, 34 | }, 35 | }, 36 | }) { 37 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 38 | ShowcaseFile: showcase.CalendarDefault(), 39 | PreviewCodeFile: "calendar_default.templ", 40 | }) 41 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 42 | Title: "Installation", 43 | ID: "installation", 44 | }) { 45 | @modules.ComponentUsage(modules.ComponentUsageProps{ 46 | ComponentName: "calendar", 47 | JSFiles: []string{"calendar"}, 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/ui/pages/checkbox_card.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ CheckboxCard() { 10 | @layouts.DocsLayout( 11 | "Checkbox Card", 12 | "Selectable card component that combines a checkbox with rich content for option selection.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Checkbox Card", 22 | Description: templ.Raw("Selectable card component that combines a checkbox with rich content for option selection."), 23 | Tailwind: true, 24 | Breadcrumbs: modules.Breadcrumbs{ 25 | Items: []modules.BreadcrumbItem{ 26 | { 27 | Text: "Docs", 28 | Path: "/docs", 29 | }, 30 | { 31 | Text: "Checkbox Card", 32 | }, 33 | }, 34 | }, 35 | }) { 36 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 37 | ShowcaseFile: showcase.CheckboxCardDefault(), 38 | PreviewCodeFile: "checkbox_card_default.templ", 39 | }) 40 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 41 | Title: "Installation", 42 | ID: "installation", 43 | }) { 44 | @modules.ComponentUsage(modules.ComponentUsageProps{ 45 | ComponentName: "checkboxcard", 46 | }) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/ui/pages/drawer.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Drawer() { 10 | @layouts.DocsLayout( 11 | "Drawer", 12 | "Side-anchored panel that slides in from screen edges.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | { 19 | Text: "Examples", 20 | ID: "examples", 21 | Children: []modules.TableOfContentsItem{ 22 | 23 | { 24 | Text: "Positions", 25 | ID: "positions", 26 | }, 27 | }, 28 | }, 29 | }, 30 | ) { 31 | @modules.PageWrapper(modules.PageWrapperProps{ 32 | Name: "Drawer", 33 | Description: templ.Raw("Side-anchored panel that slides in from screen edges."), 34 | Tailwind: true, 35 | VanillaJS: true, 36 | Breadcrumbs: modules.Breadcrumbs{ 37 | Items: []modules.BreadcrumbItem{ 38 | { 39 | Text: "Docs", 40 | Path: "/docs", 41 | }, 42 | { 43 | Text: "Drawer", 44 | }, 45 | }, 46 | }, 47 | }) { 48 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 49 | ShowcaseFile: showcase.DrawerDefault(), 50 | PreviewCodeFile: "drawer_default.templ", 51 | }) 52 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 53 | Title: "Installation", 54 | ID: "installation", 55 | }) { 56 | @modules.ComponentUsage(modules.ComponentUsageProps{ 57 | ComponentName: "drawer", 58 | JSFiles: []string{"drawer"}, 59 | }) 60 | } 61 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 62 | Title: "Examples", 63 | ID: "examples", 64 | }) { 65 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 66 | SectionName: "Positions", 67 | ShowcaseFile: showcase.DrawerPositions(), 68 | PreviewCodeFile: "drawer_positions.templ", 69 | ID: "positions", 70 | }) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/ui/pages/dropdown.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Dropdown() { 10 | @layouts.DocsLayout( 11 | "Dropdown", 12 | "Floating menu for displaying a list of actions or options. Uses Popover for the popup.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Dropdown", 22 | Description: templ.Raw("Floating menu for displaying a list of actions or options. Uses Popover for the popup."), 23 | Tailwind: true, 24 | VanillaJS: true, 25 | Breadcrumbs: modules.Breadcrumbs{ 26 | Items: []modules.BreadcrumbItem{ 27 | { 28 | Text: "Docs", 29 | Path: "/docs", 30 | }, 31 | { 32 | Text: "Dropdown", 33 | }, 34 | }, 35 | }, 36 | }) { 37 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 38 | ShowcaseFile: showcase.DropdownDefault(), 39 | PreviewCodeFile: "dropdown_default.templ", 40 | }) 41 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 42 | Title: "Installation", 43 | ID: "installation", 44 | }) { 45 | @modules.ComponentUsage(modules.ComponentUsageProps{ 46 | ComponentName: "dropdown", 47 | JSFiles: []string{"dropdown", "popover"}, 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/ui/pages/modal.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Modal() { 10 | @layouts.DocsLayout( 11 | "Modal", 12 | "Dialog overlay that requires user attention or interaction.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Modal", 22 | Description: templ.Raw("Dialog overlay that requires user attention or interaction."), 23 | Tailwind: true, 24 | VanillaJS: true, 25 | Breadcrumbs: modules.Breadcrumbs{ 26 | Items: []modules.BreadcrumbItem{ 27 | { 28 | Text: "Docs", 29 | Path: "/docs", 30 | }, 31 | { 32 | Text: "Modal", 33 | }, 34 | }, 35 | }, 36 | }) { 37 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 38 | ShowcaseFile: showcase.ModalDefault(), 39 | PreviewCodeFile: "modal_default.templ", 40 | }) 41 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 42 | Title: "Installation", 43 | ID: "installation", 44 | }) { 45 | @modules.ComponentUsage(modules.ComponentUsageProps{ 46 | ComponentName: "modal", 47 | JSFiles: []string{"modal"}, 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/ui/pages/pagination.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Pagination() { 10 | @layouts.DocsLayout( 11 | "Pagination", 12 | "Navigation controls for moving between pages of content. HTMX ready.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | { 19 | Text: "Examples", 20 | ID: "examples", 21 | Children: []modules.TableOfContentsItem{ 22 | { 23 | Text: "With Helper", 24 | ID: "with-helper", 25 | }, 26 | }, 27 | }, 28 | }, 29 | ) { 30 | @modules.PageWrapper(modules.PageWrapperProps{ 31 | Name: "Pagination", 32 | Description: templ.Raw("Navigation controls for moving between pages of content. HTMX ready."), 33 | Tailwind: true, 34 | Breadcrumbs: modules.Breadcrumbs{ 35 | Items: []modules.BreadcrumbItem{ 36 | { 37 | Text: "Docs", 38 | Path: "/docs", 39 | }, 40 | { 41 | Text: "Pagination", 42 | }, 43 | }, 44 | }, 45 | }) { 46 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 47 | ShowcaseFile: showcase.PaginationDefault(), 48 | PreviewCodeFile: "pagination_default.templ", 49 | }) 50 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 51 | Title: "Installation", 52 | ID: "installation", 53 | }) { 54 | @modules.ComponentUsage(modules.ComponentUsageProps{ 55 | ComponentName: "pagination", 56 | }) 57 | } 58 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 59 | Title: "Examples", 60 | ID: "examples", 61 | }) { 62 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 63 | SectionName: "With Helper", 64 | ShowcaseFile: showcase.PaginationWithHelper(), 65 | PreviewCodeFile: "pagination_with_helper.templ", 66 | ID: "with-helper", 67 | }) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/ui/pages/radio_card.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ RadioCard() { 10 | @layouts.DocsLayout( 11 | "Radio Card", 12 | "Selectable card component that uses radio buttons for single-option selection.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Radio Card", 22 | Description: templ.Raw("Selectable card component that uses radio buttons for single-option selection."), 23 | Tailwind: true, 24 | Breadcrumbs: modules.Breadcrumbs{ 25 | Items: []modules.BreadcrumbItem{ 26 | { 27 | Text: "Docs", 28 | Path: "/docs", 29 | }, 30 | { 31 | Text: "Radio Card", 32 | }, 33 | }, 34 | }, 35 | }) { 36 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 37 | ShowcaseFile: showcase.RadioCardDefault(), 38 | PreviewCodeFile: "radio_card_default.templ", 39 | }) 40 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 41 | Title: "Installation", 42 | ID: "installation", 43 | }) { 44 | @modules.ComponentUsage(modules.ComponentUsageProps{ 45 | ComponentName: "radiocard", 46 | }) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/ui/pages/table.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Table() { 10 | @layouts.DocsLayout( 11 | "Table", 12 | "Display tabular data with rich formatting and interaction options", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Table", 22 | Description: templ.Raw("Display tabular data with rich formatting and interaction options"), 23 | Tailwind: true, 24 | Breadcrumbs: modules.Breadcrumbs{ 25 | Items: []modules.BreadcrumbItem{ 26 | { 27 | Text: "Docs", 28 | Path: "/docs", 29 | }, 30 | { 31 | Text: "Table", 32 | }, 33 | }, 34 | }, 35 | }) { 36 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 37 | ShowcaseFile: showcase.Table(), 38 | PreviewCodeFile: "table.templ", 39 | }) 40 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 41 | Title: "Installation", 42 | ID: "installation", 43 | }) { 44 | @modules.ComponentUsage(modules.ComponentUsageProps{ 45 | ComponentName: "table", 46 | }) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/ui/pages/tabs.templ: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/ui/layouts" 5 | "github.com/axzilla/templui/internal/ui/modules" 6 | "github.com/axzilla/templui/internal/ui/showcase" 7 | ) 8 | 9 | templ Tabs() { 10 | @layouts.DocsLayout( 11 | "Tabs", 12 | "Navigation interface that organizes content into sections.", 13 | []modules.TableOfContentsItem{ 14 | { 15 | ID: "installation", 16 | Text: "Installation", 17 | }, 18 | }, 19 | ) { 20 | @modules.PageWrapper(modules.PageWrapperProps{ 21 | Name: "Tabs", 22 | Description: templ.Raw("Navigation interface that organizes content into sections."), 23 | Tailwind: true, 24 | VanillaJS: true, 25 | Breadcrumbs: modules.Breadcrumbs{ 26 | Items: []modules.BreadcrumbItem{ 27 | { 28 | Text: "Docs", 29 | Path: "/docs", 30 | }, 31 | { 32 | Text: "Tabs", 33 | }, 34 | }, 35 | }, 36 | }) { 37 | @modules.ExampleWrapper(modules.ExampleWrapperProps{ 38 | ShowcaseFile: showcase.TabsDefault(), 39 | PreviewCodeFile: "tabs_default.templ", 40 | }) 41 | @modules.ContainerWrapper(modules.ContainerWrapperProps{ 42 | Title: "Installation", 43 | ID: "installation", 44 | }) { 45 | @modules.ComponentUsage(modules.ComponentUsageProps{ 46 | ComponentName: "tabs", 47 | JSFiles: []string{"tabs"}, 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/ui/showcase/accordion_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/accordion" 4 | 5 | templ AccordionDefault() { 6 |
7 | @accordion.Accordion(accordion.Props{ 8 | Class: "w-full", 9 | }) { 10 | @accordion.Item() { 11 | @accordion.Trigger() { 12 | Is it accessible? 13 | } 14 | @accordion.Content() { 15 | Yes. It adheres to the WAI-ARIA design pattern. 16 | } 17 | } 18 | @accordion.Item() { 19 | @accordion.Trigger() { 20 | Is it styled? 21 | } 22 | @accordion.Content() { 23 | Yes. It comes with default styles that matches the other components aesthetic. 24 | } 25 | } 26 | @accordion.Item() { 27 | @accordion.Trigger() { 28 | Is it animated? 29 | } 30 | @accordion.Content() { 31 | Yes. It is animated by default, but you can disable it if you prefer. 32 | } 33 | } 34 | } 35 |
36 | } 37 | -------------------------------------------------------------------------------- /internal/ui/showcase/alert_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/alert" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ AlertDefault() { 9 |
10 | @alert.Alert() { 11 | @icon.Rocket(icon.Props{Size: 16}) 12 | @alert.Title() { 13 | Note 14 | } 15 | @alert.Description() { 16 | This is a default alert — check it out! 17 | } 18 | } 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/alert_destructive.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/alert" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ AlertDestructive() { 9 |
10 | @alert.Alert(alert.Props{Variant: alert.VariantDestructive}) { 11 | @icon.TriangleAlert(icon.Props{Size: 16}) 12 | @alert.Title() { 13 | Error 14 | } 15 | @alert.Description() { 16 | Your session has expired. Please log in again. 17 | } 18 | } 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/aspect_ratio_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/aspectratio" 4 | 5 | templ AspectRatioDefault() { 6 | @aspectratio.AspectRatio(aspectratio.Props{ 7 | Ratio: aspectratio.RatioVideo, 8 | Class: "rounded-md overflow-hidden", 9 | }) { 10 | Example image 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/ui/showcase/avatar_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/avatar" 4 | 5 | templ AvatarDefault() { 6 | @avatar.Avatar() { 7 | @avatar.Image(avatar.ImageProps{ 8 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/avatar_fallback.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/avatar" 4 | 5 | templ AvatarFallback() { 6 | @avatar.Avatar() { 7 | @avatar.Image(avatar.ImageProps{ 8 | // simulate a broken image 9 | Src: "broken-image.jpg", 10 | }) 11 | @avatar.Fallback() { 12 | { avatar.Initials("John Doe") } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/avatar_group.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/avatar" 4 | 5 | templ AvatarGroup() { 6 | @avatar.Group(avatar.GroupProps{ 7 | Spacing: avatar.GroupSpacingLg, 8 | }) { 9 | @avatar.Avatar(avatar.Props{ 10 | InGroup: true, 11 | }) { 12 | @avatar.Image(avatar.ImageProps{ 13 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 14 | }) 15 | } 16 | @avatar.Avatar(avatar.Props{ 17 | InGroup: true, 18 | }) { 19 | @avatar.Image(avatar.ImageProps{ 20 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 21 | }) 22 | } 23 | @avatar.Avatar(avatar.Props{ 24 | InGroup: true, 25 | }) { 26 | @avatar.Image(avatar.ImageProps{ 27 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 28 | }) 29 | } 30 | @avatar.GroupOverflow(2, avatar.Props{ 31 | InGroup: true, 32 | }) { 33 | @avatar.Image(avatar.ImageProps{ 34 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 35 | }) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/ui/showcase/avatar_sizes.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/avatar" 4 | 5 | templ AvatarSizes() { 6 |
7 | @avatar.Avatar(avatar.Props{ 8 | Size: avatar.SizeSm, 9 | }) { 10 | @avatar.Image(avatar.ImageProps{ 11 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 12 | }) 13 | } 14 | @avatar.Avatar() { 15 | @avatar.Image(avatar.ImageProps{ 16 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 17 | }) 18 | } 19 | @avatar.Avatar(avatar.Props{ 20 | Size: avatar.SizeLg, 21 | }) { 22 | @avatar.Image(avatar.ImageProps{ 23 | Src: "https://avatars.githubusercontent.com/u/26936893?v=4", 24 | }) 25 | } 26 |
27 | } 28 | -------------------------------------------------------------------------------- /internal/ui/showcase/avatar_with_icon.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/avatar" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ AvatarWithIcon() { 9 | @avatar.Avatar(avatar.Props{ 10 | Class: "bg-purple-300", 11 | }) { 12 | @icon.Camera(icon.Props{ 13 | Size: 22, 14 | Color: "white", 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/ui/showcase/badge_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/badge" 4 | 5 | templ BadgeDefault() { 6 | @badge.Badge() { 7 | Badge 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/badge_destructive.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/badge" 4 | 5 | templ BadgeDestructive() { 6 | @badge.Badge(badge.Props{ 7 | Variant: badge.VariantDestructive, 8 | }) { 9 | Destructive 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/badge_outline.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/badge" 4 | 5 | templ BadgeOutline() { 6 | @badge.Badge(badge.Props{ 7 | Variant: badge.VariantOutline, 8 | }) { 9 | Outline 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/badge_secondary.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/badge" 4 | 5 | templ BadgeSecondary() { 6 | @badge.Badge(badge.Props{ 7 | Variant: badge.VariantSecondary, 8 | }) { 9 | Secondary 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/badge_with_icon.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/badge" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ BadgeWithIcon() { 9 | @badge.Badge(badge.Props{ 10 | Class: "flex gap-1 items-center", 11 | }) { 12 | @icon.Rocket(icon.Props{Size: 14}) 13 | With Icon 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/breadcrumb_custom_separator.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/breadcrumb" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ BreadcrumbCustomSeparator() { 9 | @breadcrumb.Breadcrumb() { 10 | @breadcrumb.List() { 11 | @breadcrumb.Item() { 12 | @breadcrumb.Link(breadcrumb.LinkProps{ 13 | Href: "/", 14 | }) { 15 | Home 16 | } 17 | } 18 | @breadcrumb.Item() { 19 | @breadcrumb.Separator(breadcrumb.SeparatorProps{UseCustom: true}) { 20 | @icon.Slash(icon.Props{Size: 14}) 21 | } 22 | @breadcrumb.Link(breadcrumb.LinkProps{ 23 | Href: "/products", 24 | }) { 25 | Products 26 | } 27 | } 28 | @breadcrumb.Item() { 29 | @breadcrumb.Separator(breadcrumb.SeparatorProps{UseCustom: true}) { 30 | @icon.Slash(icon.Props{Size: 14}) 31 | } 32 | @breadcrumb.Page(breadcrumb.ItemProps{Current: true}) { 33 | Category 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/ui/showcase/breadcrumb_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/breadcrumb" 4 | 5 | templ BreadcrumbDefault() { 6 | @breadcrumb.Breadcrumb() { 7 | @breadcrumb.List() { 8 | @breadcrumb.Item() { 9 | @breadcrumb.Link(breadcrumb.LinkProps{ 10 | Href: "/", 11 | }) { 12 | Home 13 | } 14 | } 15 | @breadcrumb.Item() { 16 | @breadcrumb.Separator() 17 | @breadcrumb.Link(breadcrumb.LinkProps{ 18 | Href: "/docs", 19 | }) { 20 | Documentation 21 | } 22 | } 23 | @breadcrumb.Item() { 24 | @breadcrumb.Separator() 25 | @breadcrumb.Page(breadcrumb.ItemProps{Current: true}) { 26 | Components 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/ui/showcase/breadcrumb_responsive.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/breadcrumb" 4 | 5 | templ BreadcrumbResponsive() { 6 | 7 |
8 | @breadcrumb.Breadcrumb() { 9 | @breadcrumb.List() { 10 | @breadcrumb.Item() { 11 | @breadcrumb.Link(breadcrumb.LinkProps{ 12 | Href: "/", 13 | }) { 14 | Home 15 | } 16 | } 17 | @breadcrumb.Separator() 18 | @breadcrumb.Item() { 19 | @breadcrumb.Link(breadcrumb.LinkProps{ 20 | Href: "#", 21 | }) { 22 | ... 23 | } 24 | } 25 | @breadcrumb.Separator() 26 | @breadcrumb.Item() { 27 | @breadcrumb.Page(breadcrumb.ItemProps{Current: true}) { 28 | Current Page 29 | } 30 | } 31 | } 32 | } 33 |
34 | 35 | 70 | } 71 | -------------------------------------------------------------------------------- /internal/ui/showcase/breadcrumb_with_icons.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/breadcrumb" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ BreadcrumbWithIcons() { 9 | @breadcrumb.Breadcrumb() { 10 | @breadcrumb.List() { 11 | @breadcrumb.Item() { 12 | @breadcrumb.Link(breadcrumb.LinkProps{ 13 | Href: "/", 14 | }) { 15 | @icon.House(icon.Props{Size: 16}) 16 | Home 17 | } 18 | } 19 | @breadcrumb.Item() { 20 | @breadcrumb.Separator() 21 | @breadcrumb.Link(breadcrumb.LinkProps{ 22 | Href: "/docs", 23 | }) { 24 | @icon.FileText(icon.Props{Size: 16}) 25 | Documentation 26 | } 27 | } 28 | @breadcrumb.Item() { 29 | @breadcrumb.Separator() 30 | @breadcrumb.Page(breadcrumb.ItemProps{Current: true}) { 31 | @icon.Component(icon.Props{Size: 16}) 32 | Components 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonDefault() { 6 | @button.Button() { 7 | Button 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_destructive.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonDestructive() { 6 | @button.Button(button.Props{ 7 | Variant: button.VariantDestructive, 8 | }) { 9 | Destructive 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_ghost.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonGhost() { 6 | @button.Button(button.Props{ 7 | Variant: button.VariantGhost, 8 | }) { 9 | Ghost 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_htmx_loading.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/spinner" 6 | ) 7 | 8 | templ ButtonHtmxLoading() { 9 |
10 |
17 | @button.Button(button.Props{ 18 | Type: button.TypeSubmit, 19 | Class: "flex items-center gap-2", 20 | }) { 21 | 27 | Submit 28 | } 29 |
30 |
31 |
32 | } 33 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_icon.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ ButtonIcon() { 9 | @button.Button(button.Props{ 10 | Size: button.SizeIcon, 11 | Variant: button.VariantOutline, 12 | }) { 13 | @icon.ChevronRight(icon.Props{Size: 16}) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_link.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonLink() { 6 | @button.Button(button.Props{ 7 | Variant: button.VariantLink, 8 | }) { 9 | Link 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_loading.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/spinner" 6 | ) 7 | 8 | templ ButtonLoading() { 9 | @button.Button(button.Props{ 10 | Disabled: true, 11 | Class: "flex items-center gap-2", 12 | }) { 13 | @spinner.Spinner(spinner.Props{ 14 | Size: spinner.SizeSm, 15 | Color: "text-primary-foreground", 16 | }) 17 | Please wait 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_outline.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonOutline() { 6 | @button.Button(button.Props{ 7 | Variant: button.VariantOutline, 8 | }) { 9 | Outline 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_primary.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonPrimary() { 6 | @button.Button() { 7 | Primary 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_secondary.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ButtonSecondary() { 6 | @button.Button(button.Props{ 7 | Variant: button.VariantSecondary, 8 | }) { 9 | Secondary 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/button_with_icon.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ ButtonWithIcon() { 9 | @button.Button(button.Props{ 10 | Class: "flex gap-2 items-center", 11 | }) { 12 | @icon.Mail(icon.Props{Size: 16}) 13 | Login with Email 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/calendar_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/calendar" 5 | "github.com/axzilla/templui/internal/components/card" 6 | ) 7 | 8 | templ CalendarDefault() { 9 |
10 | @card.Card() { 11 | @card.Content() { 12 | @calendar.Calendar() 13 | } 14 | } 15 |
16 | } 17 | -------------------------------------------------------------------------------- /internal/ui/showcase/card_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/card" 6 | "github.com/axzilla/templui/internal/components/input" 7 | "github.com/axzilla/templui/internal/components/label" 8 | "github.com/axzilla/templui/internal/components/selectbox" 9 | ) 10 | 11 | templ CardDefault() { 12 |
13 | @card.Card() { 14 | @card.Header() { 15 | @card.Title() { 16 | Create Project 17 | } 18 | @card.Description() { 19 | Deploy your new project in one-click. 20 | } 21 | } 22 | @card.Content() { 23 |
24 |
25 | @label.Label(label.Props{ 26 | For: "name", 27 | }) { 28 | Name 29 | } 30 | @input.Input(input.Props{ 31 | ID: "name", 32 | Placeholder: "Enter project name", 33 | }) 34 |
35 |
36 | @label.Label(label.Props{ 37 | For: "service", 38 | }) { 39 | Service 40 | } 41 | @selectbox.SelectBox() { 42 | @selectbox.Trigger(selectbox.TriggerProps{ 43 | ID: "service", 44 | }) { 45 | @selectbox.Value(selectbox.ValueProps{ 46 | Placeholder: "Select", 47 | }) 48 | } 49 | @selectbox.Content() { 50 | @selectbox.Group() { 51 | @selectbox.Item(selectbox.ItemProps{ 52 | Value: "postgres", 53 | }) { 54 | PostgreSQL 55 | } 56 | @selectbox.Item(selectbox.ItemProps{ 57 | Value: "mysql", 58 | }) { 59 | MySQL 60 | } 61 | @selectbox.Item(selectbox.ItemProps{ 62 | Value: "sqlite", 63 | }) { 64 | SQLite 65 | } 66 | } 67 | } 68 | } 69 |
70 |
71 | } 72 | @card.Footer(card.FooterProps{ 73 | Class: "flex justify-between", 74 | }) { 75 | @button.Button(button.Props{ 76 | Variant: button.VariantSecondary, 77 | }) { 78 | Cancel 79 | } 80 | @button.Button() { 81 | Deploy 82 | } 83 | } 84 | } 85 |
86 | } 87 | -------------------------------------------------------------------------------- /internal/ui/showcase/card_image_bottom.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/aspectratio" 5 | "github.com/axzilla/templui/internal/components/button" 6 | "github.com/axzilla/templui/internal/components/card" 7 | ) 8 | 9 | templ CardImageBottom() { 10 |
11 | @card.Card() { 12 | @card.Header() { 13 | @card.Title() { 14 | Featured Card 15 | } 16 | @card.Description() { 17 | With bottom image 18 | } 19 | } 20 | @card.Content() { 21 |

This card shows bottom image usage.

22 | } 23 | @card.Footer() { 24 | @button.Button() { 25 | Learn more 26 | } 27 | } 28 | @card.Media(card.MediaProps{ 29 | ID: "bottom-media", 30 | Alt: "Card image", 31 | Position: card.MediaPositionBottom, 32 | AspectRatio: aspectratio.RatioVideo, 33 | Src: "/assets/img/card_placeholder.jpeg", 34 | }, 35 | ) 36 | } 37 |
38 | } 39 | -------------------------------------------------------------------------------- /internal/ui/showcase/card_image_left.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/aspectratio" 5 | "github.com/axzilla/templui/internal/components/card" 6 | ) 7 | 8 | templ CardImageLeft() { 9 |
10 | @card.Card() { 11 | @card.Horizontal() { 12 | @card.Media(card.MediaProps{ 13 | ID: "left-media", 14 | Alt: "Left side image", 15 | Position: card.MediaPositionLeft, 16 | Width: card.MediaWidthThird, 17 | AspectRatio: aspectratio.RatioAuto, 18 | Src: "/assets/img/card_placeholder.jpeg", 19 | }, 20 | ) 21 |
22 | @card.Header() { 23 | @card.Title() { 24 | Side Image Card 25 | } 26 | @card.Description() { 27 | With left-aligned image 28 | } 29 | } 30 | @card.Content() { 31 |

This card demonstrates the left image layout.

32 | } 33 |
34 | } 35 | } 36 |
37 | } 38 | -------------------------------------------------------------------------------- /internal/ui/showcase/card_image_right.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/aspectratio" 5 | "github.com/axzilla/templui/internal/components/card" 6 | ) 7 | 8 | templ CardImageRight() { 9 |
10 | @card.Card() { 11 | @card.Horizontal() { 12 |
13 | @card.Header() { 14 | @card.Title() { 15 | Side Image Card 16 | } 17 | @card.Description() { 18 | With right-aligned image 19 | } 20 | } 21 | @card.Content() { 22 |

This card demonstrates the right image layout.

23 | } 24 |
25 | @card.Media(card.MediaProps{ 26 | ID: "right-media", 27 | Alt: "Right side image", 28 | Position: card.MediaPositionRight, 29 | Width: card.MediaWidthThird, 30 | AspectRatio: aspectratio.RatioAuto, 31 | Src: "/assets/img/card_placeholder.jpeg", 32 | }, 33 | ) 34 | } 35 | } 36 |
37 | } 38 | -------------------------------------------------------------------------------- /internal/ui/showcase/card_image_top.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/aspectratio" 5 | "github.com/axzilla/templui/internal/components/button" 6 | "github.com/axzilla/templui/internal/components/card" 7 | ) 8 | 9 | templ CardImageTop() { 10 |
11 | @card.Card() { 12 | @card.Media(card.MediaProps{ 13 | ID: "top-media", 14 | Alt: "Card image", 15 | Position: card.MediaPositionTop, 16 | AspectRatio: aspectratio.RatioVideo, 17 | Src: "/assets/img/card_placeholder.jpeg", 18 | }, 19 | ) 20 | @card.Header() { 21 | @card.Title() { 22 | Featured Card 23 | } 24 | @card.Description() { 25 | With top image 26 | } 27 | } 28 | @card.Content() { 29 |

This card shows top image usage.

30 | } 31 | @card.Footer() { 32 | @button.Button() { 33 | Learn more 34 | } 35 | } 36 | } 37 |
38 | } 39 | -------------------------------------------------------------------------------- /internal/ui/showcase/carousel_autoplay.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/carousel" 4 | 5 | templ CarouselAutoplay() { 6 | @carousel.Carousel(carousel.Props{ 7 | Autoplay: true, 8 | Interval: 3000, 9 | Loop: true, 10 | Class: "rounded-md", 11 | }) { 12 | @carousel.Content() { 13 | @carousel.Item() { 14 | @CarouselAutoplaySlide("Slide 1", "This is the first slide", "bg-blue-500") 15 | } 16 | @carousel.Item() { 17 | @CarouselAutoplaySlide("Slide 2", "This is the second slide", "bg-green-500") 18 | } 19 | @carousel.Item() { 20 | @CarouselAutoplaySlide("Slide 3", "This is the third slide", "bg-purple-500") 21 | } 22 | } 23 | @carousel.Previous() 24 | @carousel.Next() 25 | @carousel.Indicators(carousel.IndicatorsProps{ 26 | Count: 3, 27 | }) 28 | } 29 | } 30 | 31 | templ CarouselAutoplaySlide(title, description, bg string) { 32 |
33 |
34 |

{ title }

35 |

{ description }

36 |
37 |
38 | } 39 | -------------------------------------------------------------------------------- /internal/ui/showcase/carousel_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/carousel" 4 | 5 | templ CarouselDefault() { 6 | @carousel.Carousel(carousel.Props{ 7 | Class: "rounded-md", 8 | }) { 9 | @carousel.Content() { 10 | @carousel.Item() { 11 | @CarouselSlide("Slide 1", "This is the first slide", "bg-blue-500") 12 | } 13 | @carousel.Item() { 14 | @CarouselSlide("Slide 2", "This is the second slide", "bg-green-500") 15 | } 16 | @carousel.Item() { 17 | @CarouselSlide("Slide 3", "This is the third slide", "bg-purple-500") 18 | } 19 | } 20 | @carousel.Previous() 21 | @carousel.Next() 22 | @carousel.Indicators(carousel.IndicatorsProps{ 23 | Count: 3, 24 | }) 25 | } 26 | } 27 | 28 | templ CarouselSlide(title, description, bg string) { 29 |
30 |
31 |

{ title }

32 |

{ description }

33 |
34 |
35 | } 36 | -------------------------------------------------------------------------------- /internal/ui/showcase/carousel_minimal.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/carousel" 4 | 5 | templ CarouselMinimal() { 6 | @carousel.Carousel(carousel.Props{ 7 | Interval: 2000, 8 | Autoplay: true, 9 | Loop: true, 10 | Class: "rounded-md", 11 | }) { 12 | @carousel.Content() { 13 | @carousel.Item() { 14 | @CarouselMinimalSlide("Slide 1", "This is the first slide", "bg-blue-500") 15 | } 16 | @carousel.Item() { 17 | @CarouselMinimalSlide("Slide 2", "This is the second slide", "bg-green-500") 18 | } 19 | @carousel.Item() { 20 | @CarouselMinimalSlide("Slide 3", "This is the third slide", "bg-purple-500") 21 | } 22 | } 23 | } 24 | } 25 | 26 | templ CarouselMinimalSlide(title, description, bg string) { 27 |
28 |
29 |

{ title }

30 |

{ description }

31 |
32 |
33 | } 34 | -------------------------------------------------------------------------------- /internal/ui/showcase/carousel_with_images.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/aspectratio" 5 | "github.com/axzilla/templui/internal/components/carousel" 6 | ) 7 | 8 | templ CarouselWithImages() { 9 | @carousel.Carousel(carousel.Props{ 10 | Autoplay: true, 11 | Interval: 5000, 12 | Loop: true, 13 | Class: "rounded-md overflow-hidden shadow-md", 14 | }) { 15 | @carousel.Content() { 16 | @carousel.Item() { 17 | @ImageSlide("/assets/img/demo/carousel-1.jpeg", "Image 1") 18 |
19 |

Nature landscape example 1

20 |
21 | } 22 | @carousel.Item() { 23 | @ImageSlide("/assets/img/demo/carousel-2.jpeg", "Image 2") 24 |
25 |

Nature landscape example 2

26 |
27 | } 28 | @carousel.Item() { 29 | @ImageSlide("/assets/img/demo/carousel-3.jpeg", "Image 3") 30 |
31 |

Nature landscape example 3

32 |
33 | } 34 | } 35 | @carousel.Previous() 36 | @carousel.Next() 37 | @carousel.Indicators(carousel.IndicatorsProps{ 38 | Count: 3, 39 | }) 40 | } 41 | } 42 | 43 | templ ImageSlide(src string, alt string) { 44 | @aspectratio.AspectRatio(aspectratio.Props{ 45 | Ratio: aspectratio.RatioVideo, 46 | }) { 47 | { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_area.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartArea() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{3, 9, 3, 12, 7, 8}, 20 | Tension: 0.5, 21 | BorderWidth: 1, 22 | Fill: true, 23 | }, 24 | }, 25 | }, 26 | }) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_area_linear.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartAreaLinear() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{3, 9, 3, 12, 7, 8}, 20 | BorderWidth: 1, 21 | Fill: true, 22 | }, 23 | }, 24 | }, 25 | }) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_area_stacked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartAreaStacked() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{3, 9, 3, 12, 7, 8}, 20 | BorderWidth: 1, 21 | Fill: true, 22 | Tension: 0.5, 23 | Label: "Mobile", 24 | }, 25 | { 26 | Data: []float64{7, 16, 5, 20, 14, 15}, 27 | BorderWidth: 1, 28 | Fill: true, 29 | Tension: 0.5, 30 | Label: "Mobile", 31 | }, 32 | }, 33 | }, 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_area_step.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartAreaStep() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{3, 9, 3, 12, 7, 8}, 20 | BorderWidth: 1, 21 | Fill: true, 22 | Stepped: true, 23 | }, 24 | }, 25 | }, 26 | }) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_bar_horizontal.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartBarHorizontal() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantBar, 13 | Horizontal: true, 14 | ShowXGrid: true, 15 | ShowYLabels: true, 16 | Data: chart.Data{ 17 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 18 | Datasets: []chart.Dataset{ 19 | { 20 | Data: []float64{12, 19, 12, 5, 2, 3}, 21 | }, 22 | }, 23 | }, 24 | }) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_bar_multiple.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartBarMultiple() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantBar, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Label: "Mobile", 20 | Data: []float64{12, 19, 12, 5, 2, 3}, 21 | }, 22 | { 23 | Label: "Desktop", 24 | Data: []float64{3, 9, 18, 3, 21, 13}, 25 | }, 26 | }, 27 | }, 28 | }) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_bar_negative.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartBarNegative() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantBar, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{12, 19, -12, 5, -2, 3}, 20 | }, 21 | }, 22 | }, 23 | }) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_bar_stacked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartBarStacked() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantBar, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Stacked: true, 16 | Data: chart.Data{ 17 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 18 | Datasets: []chart.Dataset{ 19 | { 20 | Label: "Mobile", 21 | Data: []float64{12, 19, 12, 5, 2, 3}, 22 | }, 23 | { 24 | Label: "Desktop", 25 | Data: []float64{3, 9, 18, 3, 21, 13}, 26 | }, 27 | }, 28 | }, 29 | }) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartDefault() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantBar, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{12, 19, 12, 5, 2, 3}, 20 | }, 21 | }, 22 | }, 23 | }) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_doughnut.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartDoughnut() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantDoughnut, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{7, 16, 5, 20, 14, 15}, 18 | }, 19 | }, 20 | }, 21 | }) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_doughnut_legend.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartDoughnutLegend() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantDoughnut, 13 | ShowLegend: true, 14 | Data: chart.Data{ 15 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 16 | Datasets: []chart.Dataset{ 17 | { 18 | Data: []float64{3, 9, 3, 12, 7, 8}, 19 | Label: "Mobile", 20 | }, 21 | }, 22 | }, 23 | }) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_doughnut_stacked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartDoughnutStacked() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantDoughnut, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{3, 9, 3, 12, 7, 8}, 18 | Label: "Mobile", 19 | }, 20 | { 21 | Data: []float64{7, 16, 5, 20, 14, 15}, 22 | Label: "Desktop", 23 | }, 24 | }, 25 | }, 26 | }) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_line.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartLine() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{12, 3, 9, 3, 12, 7}, 20 | Tension: 0.5, 21 | }, 22 | }, 23 | }, 24 | }) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_line_linear.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartLineLinear() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{12, 3, 9, 3, 12, 7}, 20 | }, 21 | }, 22 | }, 23 | }) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_line_multiple.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartLineMultiple() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Label: "Mobile", 20 | Data: []float64{12, 3, 9, 3, 12, 7}, 21 | Tension: 0.5, 22 | }, 23 | { 24 | Label: "Desktop", 25 | Data: []float64{7, 14, 12, 21, 2, 9}, 26 | Tension: 0.5, 27 | }, 28 | }, 29 | }, 30 | }) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_line_step.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartLineStep() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantLine, 13 | ShowYGrid: true, 14 | ShowXLabels: true, 15 | Data: chart.Data{ 16 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 17 | Datasets: []chart.Dataset{ 18 | { 19 | Data: []float64{12, 3, 9, 3, 12, 7}, 20 | Stepped: true, 21 | }, 22 | }, 23 | }, 24 | }) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_pie.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartPie() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantPie, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{3, 9, 3, 12, 7, 8}, 18 | }, 19 | }, 20 | }, 21 | }) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_pie_legend.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartPieLegend() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantPie, 13 | ShowLegend: true, 14 | Data: chart.Data{ 15 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 16 | Datasets: []chart.Dataset{ 17 | { 18 | Data: []float64{7, 16, 5, 20, 14, 15}, 19 | }, 20 | }, 21 | }, 22 | }) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_pie_stacked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartPieStacked() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantPie, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{3, 9, 3, 12, 7, 8}, 18 | Label: "Mobile", 19 | }, 20 | { 21 | Data: []float64{7, 16, 5, 20, 14, 15}, 22 | Label: "Desktop", 23 | }, 24 | }, 25 | }, 26 | }) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_radar.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ ChartRadar() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantRadar, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{3, 9, 3, 12, 7, 8}, 18 | }, 19 | }, 20 | }, 21 | }) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/ui/showcase/chart_radar_stacked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/card" 5 | "github.com/axzilla/templui/internal/components/chart" 6 | ) 7 | 8 | templ CharRadarStacked() { 9 | @card.Card(card.Props{Class: "max-w-sm"}) { 10 | @card.Content() { 11 | @chart.Chart(chart.Props{ 12 | Variant: chart.VariantRadar, 13 | Data: chart.Data{ 14 | Labels: []string{"Jan", "Feb", "March", "April", "May", "June"}, 15 | Datasets: []chart.Dataset{ 16 | { 17 | Data: []float64{15, 9, 3, 12, 7, 8}, 18 | Label: "Mobile", 19 | }, 20 | { 21 | Data: []float64{7, 16, 5, 20, 14, 15}, 22 | Label: "Desktop", 23 | }, 24 | }, 25 | }, 26 | }) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_card_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/icon" 4 | import "github.com/axzilla/templui/internal/components/checkboxcard" 5 | 6 | templ CheckboxCardDefault() { 7 |
8 | @checkboxcard.CheckboxCard(checkboxcard.Props{ 9 | ID: "feature-analytics", 10 | }, 11 | ) { 12 | @checkboxcard.Header() { 13 |
14 |
15 | @icon.ChartBar(icon.Props{Size: 20}) 16 |
17 |

Analytics

18 |
19 | } 20 | @checkboxcard.Description() { 21 | Real-time data analytics and reporting tools 22 | } 23 | @checkboxcard.Footer() { 24 | @radioCardPriceFooter("$5/month") 25 | } 26 | } 27 | @checkboxcard.CheckboxCard(checkboxcard.Props{ 28 | ID: "feature-storage", 29 | }, 30 | ) { 31 | @checkboxcard.Header() { 32 |
33 |
34 | @icon.Cloud(icon.Props{Size: 20}) 35 |
36 |

Cloud Storage

37 |
38 | } 39 | @checkboxcard.Description() { 40 | Secure file storage with 100GB capacity 41 | } 42 | @checkboxcard.Footer() { 43 | @radioCardPriceFooter("$3/month") 44 | } 45 | } 46 | @checkboxcard.CheckboxCard(checkboxcard.Props{ 47 | ID: "feature-api", 48 | Disabled: true, 49 | }) { 50 | @checkboxcard.Header() { 51 |
52 |
53 | @icon.Code(icon.Props{Size: 20}) 54 |
55 |

API Access

56 |
57 | } 58 | @checkboxcard.Description() { 59 | Full access to our developer API endpoints 60 | } 61 | @checkboxcard.Footer() { 62 | @radioCardPriceFooter("$8/month") 63 | } 64 | } 65 |
66 | } 67 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_checked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/checkbox" 4 | 5 | templ CheckboxChecked() { 6 | @checkbox.Checkbox(checkbox.Props{ 7 | Checked: true, 8 | }, 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_custom_icon.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/checkbox" 5 | "github.com/axzilla/templui/internal/components/icon" 6 | ) 7 | 8 | templ CheckboxCustomIcon() { 9 | @checkbox.Checkbox(checkbox.Props{ 10 | Icon: icon.Plus(icon.Props{Size: 12}), 11 | Checked: true, 12 | }, 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/checkbox" 4 | 5 | templ CheckboxDefault() { 6 | @checkbox.Checkbox() 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/checkbox" 5 | "github.com/axzilla/templui/internal/components/label" 6 | ) 7 | 8 | templ CheckboxDisabled() { 9 |
10 | @checkbox.Checkbox(checkbox.Props{ 11 | Disabled: true, 12 | ID: "checkbox-disabled", 13 | }, 14 | ) 15 | @label.Label(label.Props{ 16 | For: "checkbox-disabled", 17 | }) { 18 | Accept terms and conditions 19 | } 20 |
21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/checkbox" 5 | "github.com/axzilla/templui/internal/components/form" 6 | ) 7 | 8 | templ CheckboxForm() { 9 |
10 | @form.Item() { 11 | @form.ItemFlex() { 12 | @checkbox.Checkbox( 13 | checkbox.Props{ 14 | Name: "interests", 15 | Value: "design", 16 | ID: "c1", 17 | Checked: true, 18 | }, 19 | ) 20 | @form.Label(form.LabelProps{ 21 | For: "c1", 22 | }) { 23 | Dessign and UX 24 | } 25 | } 26 | @form.ItemFlex() { 27 | @checkbox.Checkbox(checkbox.Props{ 28 | Name: "interests", 29 | Value: "development", 30 | ID: "c2", 31 | Disabled: true, 32 | }) 33 | @form.Label(form.LabelProps{ 34 | For: "c2", 35 | }) { 36 | Development (Coming Soon) 37 | } 38 | } 39 | @form.ItemFlex() { 40 | @checkbox.Checkbox(checkbox.Props{ 41 | Name: "interests", 42 | Value: "marketing", 43 | ID: "c3", 44 | }) 45 | @form.Label(form.LabelProps{ 46 | For: "c3", 47 | }) { 48 | Business and Marketing 49 | } 50 | } 51 | @form.Description() { 52 | Choose all areas that interest you. 53 | } 54 | @form.Message(form.MessageProps{ 55 | Variant: form.MessageVariantError, 56 | }) { 57 | Please select at least one interest. 58 | } 59 | } 60 |
61 | } 62 | -------------------------------------------------------------------------------- /internal/ui/showcase/checkbox_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/checkbox" 5 | "github.com/axzilla/templui/internal/components/label" 6 | ) 7 | 8 | templ CheckboxWithLabel() { 9 |
10 | @checkbox.Checkbox(checkbox.Props{ 11 | ID: "checkbox-with-label", 12 | }) 13 | @label.Label(label.Props{ 14 | For: "checkbox-with-label", 15 | }) { 16 | Accept terms and conditions 17 | } 18 |
19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/code_copy_button.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/code" 4 | 5 | templ CodeCopyButton() { 6 |
7 | @code.Code(code.Props{ 8 | Language: "go", 9 | ShowCopyButton: true, 10 | }) { 11 | { `fmt.Println("Hello, World!")` } 12 | } 13 |
14 | } 15 | -------------------------------------------------------------------------------- /internal/ui/showcase/code_custom_size.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/code" 4 | 5 | templ CodeCustomSize() { 6 |
7 | @code.Code(code.Props{ 8 | Language: "go", 9 | ShowCopyButton: true, 10 | Size: code.SizeSm, 11 | }) { 12 | { `package main 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "net/http" 18 | ) 19 | 20 | func main() { 21 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 22 | fmt.Fprintf(w, "Hello, World!") 23 | }) 24 | 25 | fmt.Println("Server starting on :3000...") 26 | if err := http.ListenAndServe(":3000", nil); err != nil { 27 | log.Fatal(err) 28 | } 29 | }` } 30 | } 31 |
32 | } 33 | -------------------------------------------------------------------------------- /internal/ui/showcase/code_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/code" 4 | 5 | templ CodeDefault() { 6 |
7 | @code.Code(code.Props{ 8 | Language: "go", 9 | }) { 10 | { `fmt.Println("Hello, World!")` } 11 | } 12 |
13 | } 14 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_custom_placeholder.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/datepicker" 4 | 5 | templ DatePickerCustomPlaceholder() { 6 |
7 | @datepicker.DatePicker(datepicker.Props{ 8 | Placeholder: "When is your birthday?", 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/datepicker" 4 | 5 | templ DatePickerDefault() { 6 |
7 | @datepicker.DatePicker() 8 |
9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/datepicker" 4 | 5 | templ DatePickerDisabled() { 6 |
7 | @datepicker.DatePicker(datepicker.Props{ 8 | Disabled: true, 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/datepicker" 5 | "github.com/axzilla/templui/internal/components/form" 6 | ) 7 | 8 | templ DatePickerForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "date-picker-form", 13 | }) { 14 | Select a date 15 | } 16 | @datepicker.DatePicker(datepicker.Props{ 17 | ID: "date-picker-form", 18 | HasError: true, 19 | }) 20 | @form.Description() { 21 | Select a date from the calendar. 22 | } 23 | @form.Message(form.MessageProps{ 24 | Variant: form.MessageVariantError, 25 | }) { 26 | Select a valid date 27 | } 28 | } 29 |
30 | } 31 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_selected_date.templ: -------------------------------------------------------------------------------- 1 | 2 | package showcase 3 | 4 | import ( 5 | "github.com/axzilla/templui/internal/components/datepicker" 6 | "time" 7 | ) 8 | 9 | templ DatePickerSelectedDate() { 10 |
11 | @datepicker.DatePicker(datepicker.Props{ 12 | Value: time.Now(), 13 | }) 14 |
15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/date_picker_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/datepicker" 4 | 5 | templ DatePickerWithLabel() { 6 |
7 | // @label.Label(label.Props{ 8 | // For: "date-picker-with-label", 9 | // }) { 10 | // Pick a date 11 | // } 12 | @datepicker.DatePicker(datepicker.Props{ 13 | ID: "xxxx", 14 | }) 15 |
16 | } 17 | -------------------------------------------------------------------------------- /internal/ui/showcase/drawer_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/card" 6 | "github.com/axzilla/templui/internal/components/drawer" 7 | "github.com/axzilla/templui/internal/components/input" 8 | ) 9 | 10 | templ DrawerDefault() { 11 | @drawer.Drawer() { 12 | @drawer.Trigger(drawer.TriggerProps{Class: "mb-4"}) { 13 | @button.Button() { 14 | Open 15 | } 16 | } 17 | @drawer.Content(drawer.ContentProps{ 18 | Position: drawer.PositionRight, 19 | }) { 20 | @drawer.Header() { 21 | @drawer.Title() { 22 | Account 23 | } 24 | @drawer.Description() { 25 | Make changes to your account here. Click save when you are done. 26 | } 27 | } 28 | @card.Card() { 29 | @card.Content() { 30 |
31 | @input.Input(input.Props{ 32 | Type: input.TypeText, 33 | Placeholder: "Name", 34 | ID: "name", 35 | Value: "John Doe", 36 | }) 37 | @input.Input(input.Props{ 38 | Type: input.TypeText, 39 | Placeholder: "Username", 40 | ID: "username", 41 | Value: "@johndoe", 42 | }) 43 |
44 | } 45 | } 46 | @drawer.Footer() { 47 | @drawer.Close() { 48 | Cancel 49 | } 50 | @button.Button() { 51 | Save 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/ui/showcase/embed.go: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "embed" 5 | ) 6 | 7 | //go:embed *.templ 8 | var TemplFiles embed.FS 9 | -------------------------------------------------------------------------------- /internal/ui/showcase/icon_colored.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/icon" 4 | 5 | templ IconColored() { 6 | @icon.Heart(icon.Props{Size: 24, Color: "red"}) 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/icon_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/icon" 4 | 5 | templ IconDefault() { 6 | @icon.User() 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/icon_filled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/icon" 4 | 5 | templ IconFilled() { 6 | @icon.Triangle(icon.Props{Size: 24, Fill: "orange", Stroke: "orange"}) 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/icon_sizes.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/icon" 4 | 5 | templ IconSizes() { 6 |
7 | @icon.House() 8 | @icon.House(icon.Props{Size: 32}) 9 | @icon.House(icon.Props{Size: 48}) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/input" 4 | 5 | templ InputDefault() { 6 |
7 | @input.Input(input.Props{ 8 | Type: input.TypeEmail, 9 | Placeholder: "Email", 10 | }, 11 | ) 12 |
13 | } 14 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/input" 4 | 5 | templ InputDisabled() { 6 |
7 | @input.Input(input.Props{ 8 | Type: input.TypeEmail, 9 | Placeholder: "Email", 10 | Disabled: true, 11 | }, 12 | ) 13 |
14 | } 15 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_file.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/input" 4 | 5 | templ InputFile() { 6 |
7 | @input.Input(input.Props{Type: input.TypeFile}) 8 |
9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/input" 6 | ) 7 | 8 | templ InputForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "email-form", 13 | }) { 14 | Email 15 | } 16 | @input.Input(input.Props{ 17 | ID: "email-form", 18 | Type: input.TypeEmail, 19 | Placeholder: "m@example.com", 20 | HasError: true, 21 | }) 22 | @form.Description() { 23 | Enter your email address for notifications. 24 | } 25 | @form.Message(form.MessageProps{ 26 | Variant: form.MessageVariantError, 27 | }) { 28 | Please enter a valid email address 29 | } 30 | } 31 |
32 | } 33 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_custom_length.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | 5 | templ InputOTPCustomLength() { 6 | @inputotp.InputOTP(inputotp.Props{ 7 | ID: "otp-custom-length", 8 | }) { 9 | @inputotp.Group() { 10 | @inputotp.Slot(inputotp.SlotProps{ 11 | Index: 0, 12 | }) 13 | @inputotp.Slot(inputotp.SlotProps{ 14 | Index: 1, 15 | }) 16 | @inputotp.Slot(inputotp.SlotProps{ 17 | Index: 2, 18 | }) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_custom_styling.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | 5 | templ InputOTPCustomStyling() { 6 | @inputotp.InputOTP(inputotp.Props{ 7 | ID: "otp-styled", 8 | }) { 9 | @inputotp.Group(inputotp.GroupProps{ 10 | Class: "gap-3", 11 | }) { 12 | @inputotp.Slot(inputotp.SlotProps{ 13 | Index: 0, 14 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 15 | }) 16 | @inputotp.Slot(inputotp.SlotProps{ 17 | Index: 1, 18 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 19 | }) 20 | @inputotp.Slot(inputotp.SlotProps{ 21 | Index: 2, 22 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 23 | }) 24 | @inputotp.Separator(inputotp.SeparatorProps{ 25 | Class: "text-2xl font-bold text-primary", 26 | }) { 27 | : 28 | } 29 | @inputotp.Slot(inputotp.SlotProps{ 30 | Index: 3, 31 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 32 | }) 33 | @inputotp.Slot(inputotp.SlotProps{ 34 | Index: 4, 35 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 36 | }) 37 | @inputotp.Slot(inputotp.SlotProps{ 38 | Index: 5, 39 | Class: "w-12 h-14 bg-primary/10 border-primary text-lg font-bold", 40 | }) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | 5 | templ InputOTPDefault() { 6 | @inputotp.InputOTP() { 7 | @inputotp.Group() { 8 | @inputotp.Slot(inputotp.SlotProps{ 9 | Index: 0, 10 | }) 11 | @inputotp.Slot(inputotp.SlotProps{ 12 | Index: 1, 13 | }) 14 | @inputotp.Slot(inputotp.SlotProps{ 15 | Index: 2, 16 | }) 17 | @inputotp.Slot(inputotp.SlotProps{ 18 | Index: 3, 19 | }) 20 | @inputotp.Slot(inputotp.SlotProps{ 21 | Index: 4, 22 | }) 23 | @inputotp.Slot(inputotp.SlotProps{ 24 | Index: 5, 25 | }) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/inputotp" 6 | ) 7 | 8 | templ InputOTPForm() { 9 | @form.Item() { 10 | @form.Label(form.LabelProps{ 11 | For: "otp-form", 12 | }) { 13 | Verification Code 14 | } 15 | @inputotp.InputOTP(inputotp.Props{ 16 | ID: "otp-form", 17 | Required: true, 18 | HasError: true, 19 | }) { 20 | @inputotp.Group() { 21 | @inputotp.Slot(inputotp.SlotProps{ 22 | Index: 0, 23 | }) 24 | @inputotp.Slot(inputotp.SlotProps{ 25 | Index: 1, 26 | }) 27 | @inputotp.Slot(inputotp.SlotProps{ 28 | Index: 2, 29 | }) 30 | @inputotp.Separator() 31 | @inputotp.Slot(inputotp.SlotProps{ 32 | Index: 3, 33 | }) 34 | @inputotp.Slot(inputotp.SlotProps{ 35 | Index: 4, 36 | }) 37 | @inputotp.Slot(inputotp.SlotProps{ 38 | Index: 5, 39 | }) 40 | } 41 | } 42 | @form.Description() { 43 | Enter the 6-digit code sent to your phone 44 | } 45 | @form.Message(form.MessageProps{ 46 | Variant: form.MessageVariantError, 47 | }) { 48 | Invalid verification code. 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_password_type.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | 5 | templ InputOTPPasswordType() { 6 | @inputotp.InputOTP(inputotp.Props{ 7 | ID: "otp-password", 8 | }) { 9 | @inputotp.Group() { 10 | @inputotp.Slot(inputotp.SlotProps{ 11 | Index: 0, 12 | Type: "password", 13 | }) 14 | @inputotp.Slot(inputotp.SlotProps{ 15 | Index: 1, 16 | Type: "password", 17 | }) 18 | @inputotp.Slot(inputotp.SlotProps{ 19 | Index: 2, 20 | Type: "password", 21 | }) 22 | @inputotp.Separator() 23 | @inputotp.Slot(inputotp.SlotProps{ 24 | Index: 3, 25 | Type: "password", 26 | }) 27 | @inputotp.Slot(inputotp.SlotProps{ 28 | Index: 4, 29 | Type: "password", 30 | }) 31 | @inputotp.Slot(inputotp.SlotProps{ 32 | Index: 5, 33 | Type: "password", 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_placeholder.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | 5 | templ InputOTPPlaceholder() { 6 | @inputotp.InputOTP() { 7 | @inputotp.Group() { 8 | @inputotp.Slot(inputotp.SlotProps{ 9 | Index: 0, 10 | Placeholder: "•", 11 | }) 12 | @inputotp.Slot(inputotp.SlotProps{ 13 | Index: 1, 14 | Placeholder: "•", 15 | }) 16 | @inputotp.Slot(inputotp.SlotProps{ 17 | Index: 2, 18 | Placeholder: "•", 19 | }) 20 | @inputotp.Separator() 21 | @inputotp.Slot(inputotp.SlotProps{ 22 | Index: 3, 23 | Placeholder: "•", 24 | }) 25 | @inputotp.Slot(inputotp.SlotProps{ 26 | Index: 4, 27 | Placeholder: "•", 28 | }) 29 | @inputotp.Slot(inputotp.SlotProps{ 30 | Index: 5, 31 | Placeholder: "•", 32 | }) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_otp_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/inputotp" 4 | import "github.com/axzilla/templui/internal/components/label" 5 | 6 | templ InputOTPWithLabel() { 7 |
8 | @label.Label(label.Props{ 9 | For: "otp-with-label", 10 | }) { 11 | Verification Code 12 | } 13 | @inputotp.InputOTP(inputotp.Props{ 14 | ID: "otp-with-label", 15 | Required: true, 16 | HasError: true, 17 | }) { 18 | @inputotp.Group() { 19 | @inputotp.Slot(inputotp.SlotProps{ 20 | Index: 0, 21 | }) 22 | @inputotp.Slot(inputotp.SlotProps{ 23 | Index: 1, 24 | }) 25 | @inputotp.Slot(inputotp.SlotProps{ 26 | Index: 2, 27 | }) 28 | @inputotp.Slot(inputotp.SlotProps{ 29 | Index: 3, 30 | }) 31 | @inputotp.Slot(inputotp.SlotProps{ 32 | Index: 4, 33 | }) 34 | @inputotp.Slot(inputotp.SlotProps{ 35 | Index: 5, 36 | }) 37 | } 38 | } 39 |
40 | } 41 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_password.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/input" 4 | 5 | templ InputPassword() { 6 |
7 | @input.Input(input.Props{ 8 | Type: input.TypePassword, 9 | Placeholder: "your password", 10 | }) 11 |
12 | } 13 | -------------------------------------------------------------------------------- /internal/ui/showcase/input_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/input" 5 | "github.com/axzilla/templui/internal/components/label" 6 | ) 7 | 8 | templ InputWithLabel() { 9 |
10 | @label.Label(label.Props{ 11 | For: "email", 12 | }) { 13 | Email 14 | } 15 | @input.Input(input.Props{ 16 | ID: "email", 17 | Type: input.TypeEmail, 18 | Placeholder: "Email", 19 | }) 20 |
21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/showcase/modal_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/modal" 6 | ) 7 | 8 | templ ModalDefault() { 9 | @modal.Trigger(modal.TriggerProps{ 10 | ModalID: "default-modal", 11 | }) { 12 | @button.Button() { 13 | Open Modal 14 | } 15 | } 16 | @modal.Modal(modal.Props{ 17 | ID: "default-modal", 18 | Class: "max-w-md", 19 | }) { 20 | @modal.Header() { 21 | Are you absolutely sure? 22 | } 23 | @modal.Body() { 24 | This action cannot be undone. This will permanently delete your account and remove your data from our servers. 25 | } 26 | @modal.Footer() { 27 |
28 | @modal.Close(modal.CloseProps{ 29 | ModalID: "default-modal", 30 | }) { 31 | @button.Button() { 32 | Cancel 33 | } 34 | } 35 | @modal.Close(modal.CloseProps{ 36 | ModalID: "default-modal", 37 | }) { 38 | @button.Button(button.Props{ 39 | Variant: button.VariantSecondary, 40 | }) { 41 | Continue 42 | } 43 | } 44 |
45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/ui/showcase/pagination_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/pagination" 4 | 5 | templ PaginationDefault() { 6 | @pagination.Pagination(pagination.Props{ 7 | Class: "mt-8", 8 | }) { 9 | @pagination.Content() { 10 | @pagination.Item() { 11 | @pagination.Previous(pagination.PreviousProps{ 12 | Href: "?page=1", 13 | Disabled: false, 14 | Label: "Previous", 15 | }) 16 | } 17 | @pagination.Item() { 18 | @pagination.Link(pagination.LinkProps{ 19 | Href: "?page=1", 20 | }) { 21 | 1 22 | } 23 | } 24 | @pagination.Item() { 25 | @pagination.Link(pagination.LinkProps{ 26 | Href: "?page=2", 27 | IsActive: true, 28 | }) { 29 | 2 30 | } 31 | } 32 | @pagination.Item() { 33 | @pagination.Link(pagination.LinkProps{ 34 | Href: "?page=3", 35 | }) { 36 | 3 37 | } 38 | } 39 | @pagination.Item() { 40 | @pagination.Ellipsis() 41 | } 42 | @pagination.Item() { 43 | @pagination.Next(pagination.NextProps{ 44 | Href: "?page=3", 45 | Label: "Next", 46 | }) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/ui/showcase/pagination_with_helper.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "fmt" 5 | "github.com/axzilla/templui/internal/components/pagination" 6 | ) 7 | 8 | templ PaginationWithHelper() { 9 | {{ p := pagination.CreatePagination(5, 20, 3) }} 10 | @pagination.Pagination() { 11 | @pagination.Content() { 12 | @pagination.Item() { 13 | @pagination.Previous(pagination.PreviousProps{ 14 | Href: fmt.Sprintf("?page=%d", p.CurrentPage-1), 15 | Disabled: !p.HasPrevious, 16 | Label: "Previous", 17 | }) 18 | } 19 | // First page with ellipsis if needed 20 | if p.Pages[0] > 1 { 21 | @pagination.Item() { 22 | @pagination.Link(pagination.LinkProps{ 23 | Href: "?page=1", 24 | }) { 25 | 1 26 | } 27 | } 28 | if p.Pages[0] > 2 { 29 | @pagination.Item() { 30 | @pagination.Ellipsis() 31 | } 32 | } 33 | } 34 | // Visible pages 35 | for _, page := range p.Pages { 36 | @pagination.Item() { 37 | @pagination.Link(pagination.LinkProps{ 38 | Href: fmt.Sprintf("?page=%d", page), 39 | IsActive: page == p.CurrentPage, 40 | }) { 41 | { fmt.Sprint(page) } 42 | } 43 | } 44 | } 45 | // Last page with ellipsis if needed 46 | if p.Pages[len(p.Pages)-1] < p.TotalPages { 47 | if p.Pages[len(p.Pages)-1] < p.TotalPages-1 { 48 | @pagination.Item() { 49 | @pagination.Ellipsis() 50 | } 51 | } 52 | @pagination.Item() { 53 | @pagination.Link(pagination.LinkProps{ 54 | Href: fmt.Sprintf("?page=%d", p.TotalPages), 55 | }) { 56 | { fmt.Sprint(p.TotalPages) } 57 | } 58 | } 59 | } 60 | @pagination.Item() { 61 | @pagination.Next(pagination.NextProps{ 62 | Href: fmt.Sprintf("?page=%d", p.CurrentPage+1), 63 | Disabled: !p.HasNext, 64 | Label: "Next", 65 | }) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/ui/showcase/popover_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/input" 6 | "github.com/axzilla/templui/internal/components/label" 7 | "github.com/axzilla/templui/internal/components/popover" 8 | "github.com/axzilla/templui/internal/utils" 9 | ) 10 | 11 | templ PopoverDefault() { 12 | @popover.Popover() { 13 | @popover.Trigger(popover.TriggerProps{ 14 | For: "default-popover", 15 | }) { 16 | @button.Button(button.Props{ 17 | Variant: button.VariantOutline, 18 | }) { 19 | Open Popover 20 | } 21 | } 22 | @popover.Content(popover.ContentProps{ 23 | ID: "default-popover", 24 | }) { 25 | @PopoverContent() 26 | } 27 | } 28 | } 29 | 30 | templ PopoverContent() { 31 | {{ var id = utils.RandomID() }} 32 |
33 |
34 |

Dimensions

35 |

Set the dimensions for the layer.

36 |
37 |
38 |
39 | @label.Label(label.Props{ 40 | For: "width" + id, 41 | Class: "w-24", 42 | }) { 43 | Width 44 | } 45 | @input.Input(input.Props{ 46 | ID: "width" + id, 47 | Placeholder: "Width", 48 | Value: "100%", 49 | Class: "flex-1", 50 | }) 51 |
52 |
53 | @label.Label(label.Props{ 54 | For: "height" + id, 55 | Class: "w-24", 56 | }) { 57 | Height 58 | } 59 | @input.Input(input.Props{ 60 | ID: "height" + id, 61 | Placeholder: "Height", 62 | Value: "100%", 63 | Class: "flex-1", 64 | }) 65 |
66 |
67 |
68 | } 69 | -------------------------------------------------------------------------------- /internal/ui/showcase/popover_triggers.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/popover" 6 | ) 7 | 8 | templ PopoverTriggers() { 9 |
10 | @popover.Popover() { 11 | @popover.Trigger(popover.TriggerProps{ 12 | For: "hover-popover", 13 | TriggerType: popover.TriggerTypeHover, 14 | }) { 15 | @button.Button(button.Props{Variant: button.VariantOutline}) { 16 | Hover 17 | } 18 | } 19 | @popover.Content(popover.ContentProps{ 20 | ID: "hover-popover", 21 | HoverDelay: 300, 22 | HoverOutDelay: 500, 23 | }) { 24 | @PopoverContent() 25 | } 26 | } 27 | @popover.Popover() { 28 | @popover.Trigger(popover.TriggerProps{ 29 | For: "click-popover", 30 | }) { 31 | @button.Button(button.Props{Variant: button.VariantOutline}) { 32 | Click 33 | } 34 | } 35 | @popover.Content(popover.ContentProps{ 36 | ID: "click-popover", 37 | }) { 38 | @PopoverContent() 39 | } 40 | } 41 | @popover.Popover() { 42 | @popover.Trigger(popover.TriggerProps{ 43 | For: "no-clickaway-popover", 44 | }) { 45 | @button.Button(button.Props{Variant: button.VariantOutline}) { 46 | No ClickAway 47 | } 48 | } 49 | @popover.Content(popover.ContentProps{ 50 | ID: "no-clickaway-popover", 51 | DisableClickAway: true, 52 | }) { 53 | @PopoverContent() 54 | } 55 | } 56 | @popover.Popover() { 57 | @popover.Trigger(popover.TriggerProps{ 58 | For: "no-clickaway-esc", 59 | }) { 60 | @button.Button(button.Props{Variant: button.VariantOutline}) { 61 | No ClickAway-ESC 62 | } 63 | } 64 | @popover.Content(popover.ContentProps{ 65 | ID: "no-clickaway-esc", 66 | DisableClickAway: true, 67 | DisableESC: true, 68 | }) { 69 | @PopoverContent() 70 | } 71 | } 72 |
73 | } 74 | -------------------------------------------------------------------------------- /internal/ui/showcase/progress_colors.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/progress" 4 | 5 | templ ProgressColors() { 6 |
7 | @progress.Progress(progress.Props{ 8 | Value: 50, 9 | Variant: progress.VariantSuccess, 10 | }) 11 | @progress.Progress(progress.Props{ 12 | Value: 75, 13 | Variant: progress.VariantDanger, 14 | }) 15 | @progress.Progress(progress.Props{ 16 | Value: 90, 17 | Variant: progress.VariantWarning, 18 | }) 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/progress_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/progress" 4 | 5 | templ ProgressDefault() { 6 |
7 | @progress.Progress(progress.Props{ 8 | Value: 25, 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/progress_sizes.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/progress" 4 | 5 | templ ProgressSizes() { 6 |
7 | @progress.Progress(progress.Props{ 8 | Value: 50, 9 | Size: progress.SizeSm, 10 | }) 11 | @progress.Progress(progress.Props{ 12 | Value: 65, 13 | Size: progress.SizeLg, 14 | }) 15 | @progress.Progress(progress.Props{ 16 | Value: 80, 17 | Size: progress.SizeLg, 18 | }) 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_card_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/icon" 5 | "github.com/axzilla/templui/internal/components/radiocard" 6 | ) 7 | 8 | templ RadioCardDefault() { 9 |
10 | @radiocard.RadioCard(radiocard.Props{ 11 | ID: "comp-plan-basic", 12 | Name: "comp-plan", 13 | Value: "basic", 14 | }) { 15 | @radiocard.Header() { 16 |
17 | @icon.Package(icon.Props{Size: 20}) 18 |

Basic Plan

19 |
20 | } 21 | @radiocard.Description() { 22 | Essential features for individuals and small teams 23 | } 24 | @radiocard.Footer() { 25 | @radioCardPriceFooter("$5.99") 26 | } 27 | } 28 | @radiocard.RadioCard(radiocard.Props{ 29 | ID: "comp-plan-pro", 30 | Name: "comp-plan", 31 | Value: "pro", 32 | }) { 33 | @radiocard.Header() { 34 |
35 | @icon.Star(icon.Props{Size: 20}) 36 |

Pro Plan

37 |
38 | } 39 | @radiocard.Description() { 40 | Enhanced capabilities for growing businesses. 41 | } 42 | @radiocard.Footer() { 43 | @radioCardPriceFooter("$14.99") 44 | } 45 | } 46 | @radiocard.RadioCard(radiocard.Props{ 47 | ID: "comp-plan-enterprise", 48 | Name: "comp-plan", 49 | Value: "enterprise", 50 | Disabled: true, 51 | }) { 52 | @radiocard.Header() { 53 |
54 | @icon.Building(icon.Props{Size: 20}) 55 |

Enterprise Plan

56 |
57 | } 58 | @radiocard.Description() { 59 | Advanced features for large organizations 60 | } 61 | @radiocard.Footer() { 62 | @radioCardPriceFooter("$29.99") 63 | } 64 | } 65 |
66 | } 67 | 68 | templ radioCardPriceFooter(price string) { 69 |
70 | Price 71 | { price } 72 |
73 | } 74 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_checked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/radio" 4 | 5 | templ RadioChecked() { 6 | @radio.Radio(radio.Props{ 7 | Checked: true, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/radio" 4 | 5 | templ RadioDefault() { 6 | @radio.Radio() 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/radio" 6 | ) 7 | 8 | templ RadioDisabled() { 9 |
10 | @radio.Radio(radio.Props{ 11 | ID: "radio-disabled", 12 | Disabled: true, 13 | }) 14 | @label.Label(label.Props{ 15 | For: "radio-disabled", 16 | }) { 17 | Disabled 18 | } 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/radio" 6 | ) 7 | 8 | templ RadioForm() { 9 |
10 | @form.Item() { 11 | @form.ItemFlex() { 12 | @radio.Radio(radio.Props{ 13 | Name: "radio-form", 14 | ID: "r1", 15 | Checked: true, 16 | }) 17 | @form.Label(form.LabelProps{ 18 | For: "r1", 19 | }) { 20 | All new products 21 | } 22 | } 23 | @form.ItemFlex() { 24 | @radio.Radio(radio.Props{ 25 | Name: "radio-form", 26 | ID: "r2", 27 | Disabled: true, 28 | }) 29 | @form.Label(form.LabelProps{ 30 | For: "r2", 31 | }) { 32 | Create a wishlist (Coming Soon) 33 | } 34 | } 35 | @form.ItemFlex() { 36 | @radio.Radio(radio.Props{ 37 | Name: "radio-form", 38 | ID: "r3", 39 | }) 40 | @form.Label(form.LabelProps{ 41 | For: "r3", 42 | }) { 43 | No notifications 44 | } 45 | } 46 | @form.Description() { 47 | You can change your preferences at any time. 48 | } 49 | @form.Message(form.MessageProps{ 50 | Variant: form.MessageVariantError, 51 | }) { 52 | We will send you an email when new products are available. 53 | } 54 | } 55 |
56 | } 57 | -------------------------------------------------------------------------------- /internal/ui/showcase/radio_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/radio" 6 | ) 7 | 8 | templ RadioWithLabel() { 9 |
10 | @radio.Radio(radio.Props{ 11 | ID: "radio-with-label", 12 | }) 13 | @label.Label(label.Props{ 14 | For: "radio-with-label", 15 | }) { 16 | Label 17 | } 18 |
19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/rating" 4 | 5 | templ RatingDefault() { 6 | @rating.Rating(rating.Props{ 7 | Value: 3.5, 8 | ReadOnly: false, 9 | Precision: 0.5, 10 | }) { 11 | @rating.Group() { 12 | for i := 1; i <= 5; i++ { 13 | @rating.Item(rating.ItemProps{ 14 | Value: i, 15 | Style: rating.StyleStar, 16 | }) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/rating" 6 | ) 7 | 8 | templ RatingForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "product_quality", 13 | }) { 14 | Product Quality 15 | } 16 | @rating.Rating(rating.Props{ 17 | Value: 3, 18 | ReadOnly: false, 19 | Precision: 1.0, 20 | Name: "product_quality", 21 | }) { 22 | @rating.Group() { 23 | for i := 1; i <= 5; i++ { 24 | @rating.Item(rating.ItemProps{ 25 | Value: i, 26 | Style: rating.StyleStar, 27 | }) 28 | } 29 | } 30 | } 31 | @form.Description() { 32 | Rate the quality of the product you received. 33 | } 34 | @form.Message(form.MessageProps{ 35 | Variant: form.MessageVariantError, 36 | }) { 37 | Please rate the product quality. 38 | } 39 | } 40 |
41 | } 42 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_max_values.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/rating" 4 | 5 | templ RatingMaxValues() { 6 | @rating.Rating(rating.Props{ 7 | Value: 7, 8 | }) { 9 | @rating.Group() { 10 | for i := 1; i <= 10; i++ { 11 | @rating.Item(rating.ItemProps{ 12 | Value: i, 13 | Style: rating.StyleStar, 14 | Class: "scale-75", // Smaller stars for better display 15 | }) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_precision.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/rating" 4 | 5 | templ RatingPrecision() { 6 | @rating.Rating(rating.Props{ 7 | Value: 1.3, 8 | ReadOnly: true, 9 | Precision: 0.5, 10 | }) { 11 | @rating.Group() { 12 | for i := 1; i <= 5; i++ { 13 | @rating.Item(rating.ItemProps{ 14 | Value: i, 15 | Style: rating.StyleStar, 16 | }) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_styles.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/rating" 4 | 5 | templ RatingStyles() { 6 |
7 | @rating.Rating(rating.Props{ 8 | Value: 2, 9 | }) { 10 | @rating.Group() { 11 | for i := 1; i <= 5; i++ { 12 | @rating.Item(rating.ItemProps{ 13 | Value: i, 14 | Style: rating.StyleStar, 15 | }) 16 | } 17 | } 18 | } 19 | @rating.Rating(rating.Props{ 20 | Value: 3, 21 | }) { 22 | @rating.Group() { 23 | for i := 1; i <= 5; i++ { 24 | @rating.Item(rating.ItemProps{ 25 | Value: i, 26 | Style: rating.StyleHeart, 27 | }) 28 | } 29 | } 30 | } 31 | @rating.Rating(rating.Props{ 32 | Value: 4, 33 | }) { 34 | @rating.Group() { 35 | for i := 1; i <= 5; i++ { 36 | @rating.Item(rating.ItemProps{ 37 | Value: i, 38 | Style: rating.StyleEmoji, 39 | }) 40 | } 41 | } 42 | } 43 |
44 | } 45 | -------------------------------------------------------------------------------- /internal/ui/showcase/rating_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/rating" 6 | ) 7 | 8 | templ RatingWithLabel() { 9 |
10 |
11 | @label.Label(label.Props{ 12 | For: "rating-with-label", 13 | }) { 14 | Fruit 15 | } 16 | @rating.Rating(rating.Props{ 17 | Value: 2, 18 | }) { 19 | @rating.Group() { 20 | for i := 1; i <= 5; i++ { 21 | @rating.Item(rating.ItemProps{ 22 | Value: i, 23 | Style: rating.StyleStar, 24 | }) 25 | } 26 | } 27 | } 28 |
29 |
30 | } 31 | -------------------------------------------------------------------------------- /internal/ui/showcase/select_box_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/selectbox" 4 | 5 | templ SelectBoxDefault() { 6 |
7 | @selectbox.SelectBox() { 8 | @selectbox.Trigger() { 9 | @selectbox.Value(selectbox.ValueProps{ 10 | Placeholder: "Select a fruit", 11 | }) 12 | } 13 | @selectbox.Content() { 14 | @selectbox.Group() { 15 | @selectbox.Label() { 16 | Fruits 17 | } 18 | @selectbox.Item(selectbox.ItemProps{ 19 | Value: "apple", 20 | }) { 21 | Apple 22 | } 23 | @selectbox.Item(selectbox.ItemProps{ 24 | Value: "banana", 25 | }) { 26 | Banana 27 | } 28 | @selectbox.Item(selectbox.ItemProps{ 29 | Value: "blueberry", 30 | }) { 31 | Blueberry 32 | } 33 | @selectbox.Item(selectbox.ItemProps{ 34 | Value: "grapes", 35 | }) { 36 | Grapes 37 | } 38 | @selectbox.Item(selectbox.ItemProps{ 39 | Value: "pineapple", 40 | }) { 41 | Pineapple 42 | } 43 | } 44 | } 45 | } 46 |
47 | } 48 | -------------------------------------------------------------------------------- /internal/ui/showcase/select_box_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/selectbox" 4 | 5 | templ SelectBoxDisabled() { 6 |
7 | @selectbox.SelectBox() { 8 | @selectbox.Trigger(selectbox.TriggerProps{ 9 | Disabled: true, 10 | }) { 11 | @selectbox.Value(selectbox.ValueProps{ 12 | Placeholder: "Select a fruit", 13 | }) 14 | } 15 | @selectbox.Content() { 16 | @selectbox.Label() { 17 | Fruits 18 | } 19 | @selectbox.Item(selectbox.ItemProps{ 20 | Value: "apple", 21 | }) { 22 | Apple 23 | } 24 | @selectbox.Item(selectbox.ItemProps{ 25 | Value: "banana", 26 | }) { 27 | Banana 28 | } 29 | } 30 | } 31 |
32 | } 33 | -------------------------------------------------------------------------------- /internal/ui/showcase/select_box_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/selectbox" 6 | ) 7 | 8 | templ SelectBoxForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "select-form", 13 | }) { 14 | Fruit 15 | } 16 | @selectbox.SelectBox() { 17 | @selectbox.Trigger(selectbox.TriggerProps{ 18 | ID: "select-form", 19 | Name: "fruit", 20 | Required: true, 21 | HasError: true, 22 | }) { 23 | @selectbox.Value(selectbox.ValueProps{ 24 | Placeholder: "Select a fruit", 25 | }) 26 | } 27 | @selectbox.Content() { 28 | @selectbox.Item(selectbox.ItemProps{ 29 | Value: "apple", 30 | }) { 31 | Apple 32 | } 33 | @selectbox.Item(selectbox.ItemProps{ 34 | Value: "banana", 35 | }) { 36 | Banana 37 | } 38 | @selectbox.Item(selectbox.ItemProps{ 39 | Value: "blueberry", 40 | Selected: true, 41 | }) { 42 | Blueberry 43 | } 44 | @selectbox.Item(selectbox.ItemProps{ 45 | Value: "grapes", 46 | }) { 47 | Grapes 48 | } 49 | @selectbox.Item(selectbox.ItemProps{ 50 | Value: "pineapple", 51 | Disabled: true, 52 | }) { 53 | Pineapple (out of stock) 54 | } 55 | } 56 | } 57 | @form.Description() { 58 | Select a fruit category. 59 | } 60 | @form.Message(form.MessageProps{ 61 | Variant: form.MessageVariantError, 62 | }) { 63 | A fruit selection is required. 64 | } 65 | } 66 |
67 | } 68 | -------------------------------------------------------------------------------- /internal/ui/showcase/select_box_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/selectbox" 6 | ) 7 | 8 | templ SelectBoxWithLabel() { 9 |
10 | @label.Label(label.Props{ 11 | For: "select-with-label", 12 | }) { 13 | Fruit 14 | } 15 | @selectbox.SelectBox() { 16 | @selectbox.Trigger(selectbox.TriggerProps{ 17 | ID: "select-with-label", 18 | }) { 19 | @selectbox.Value(selectbox.ValueProps{ 20 | Placeholder: "Select a fruit", 21 | }) 22 | } 23 | @selectbox.Content() { 24 | @selectbox.Label() { 25 | Fruits 26 | } 27 | @selectbox.Item(selectbox.ItemProps{ 28 | Value: "apple", 29 | }) { 30 | Apple 31 | } 32 | @selectbox.Item(selectbox.ItemProps{ 33 | Value: "banana", 34 | }) { 35 | Banana 36 | } 37 | @selectbox.Item(selectbox.ItemProps{ 38 | Value: "orange", 39 | }) { 40 | Orange 41 | } 42 | } 43 | } 44 |
45 | } 46 | -------------------------------------------------------------------------------- /internal/ui/showcase/separator_decorated.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/separator" 4 | 5 | templ SeparatorDecorated() { 6 |
7 | @separator.Separator(separator.Props{ 8 | Decoration: separator.DecorationDashed, 9 | }) { 10 | DASHED 11 | } 12 | @separator.Separator(separator.Props{ 13 | Decoration: separator.DecorationDotted, 14 | }) { 15 | DOTTED 16 | } 17 |
18 | } 19 | -------------------------------------------------------------------------------- /internal/ui/showcase/separator_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/separator" 4 | 5 | templ SeparatorDefault() { 6 |
7 |

Top

8 | @separator.Separator() 9 |

Bottom

10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/separator_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/separator" 4 | 5 | templ SeparatorLabel() { 6 |
7 | @separator.Separator(separator.Props{ 8 | Class: "w-full", 9 | }) { 10 | OR 11 | } 12 | @separator.Separator(separator.Props{ 13 | Class: "h-24", 14 | Orientation: separator.OrientationVertical, 15 | }) { 16 | OR 17 | } 18 |
19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/separator_vertical.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/separator" 4 | 5 | templ SeparatorVertical() { 6 |
7 |
8 |
Left
9 | @separator.Separator(separator.Props{ 10 | Orientation: separator.OrientationVertical, 11 | Class: "mx-4", 12 | }) 13 |
Right
14 |
15 |
16 | } 17 | -------------------------------------------------------------------------------- /internal/ui/showcase/skeleton_card.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/skeleton" 4 | 5 | templ SkeletonCard() { 6 |
7 | @skeleton.Skeleton(skeleton.Props{Class: "h-[200px] w-full rounded-md mb-4"}) 8 |
9 | @skeleton.Skeleton(skeleton.Props{Class: "h-5 w-2/3"}) 10 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-full"}) 11 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-full"}) 12 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-3/4"}) 13 |
14 |
15 | } 16 | -------------------------------------------------------------------------------- /internal/ui/showcase/skeleton_dashboard.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/skeleton" 4 | 5 | templ SkeletonDashboard() { 6 |
7 |
8 | for i := 0; i < 3; i++ { 9 |
10 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-20 mb-2"}) 11 | @skeleton.Skeleton(skeleton.Props{Class: "h-8 w-24 mb-4"}) 12 |
13 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-12"}) 14 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-4"}) 15 |
16 |
17 | } 18 |
19 |
20 | @skeleton.Skeleton(skeleton.Props{Class: "h-5 w-1/3 mb-6"}) 21 | @skeleton.Skeleton(skeleton.Props{Class: "h-[240px] w-full rounded-md"}) 22 |
23 |
24 | } 25 | -------------------------------------------------------------------------------- /internal/ui/showcase/skeleton_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/skeleton" 4 | 5 | templ SkeletonDefault() { 6 |
7 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-full"}) 8 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-2/3"}) 9 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-1/3"}) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/skeleton_profile.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/skeleton" 4 | 5 | templ SkeletonProfile() { 6 |
7 | @skeleton.Skeleton(skeleton.Props{Class: "h-12 w-12 rounded-full"}) 8 |
9 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-full"}) 10 | @skeleton.Skeleton(skeleton.Props{Class: "h-4 w-3/4"}) 11 |
12 |
13 | } 14 | -------------------------------------------------------------------------------- /internal/ui/showcase/slider_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/slider" 4 | 5 | templ SliderDefault() { 6 |
7 | @slider.Slider() { 8 | @slider.Input() 9 | } 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/slider_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/slider" 6 | ) 7 | 8 | templ SliderDisabled() { 9 |
10 | @slider.Slider() { 11 |
12 | @label.Label() { 13 | Volume 14 | } 15 | @slider.Value(slider.ValueProps{ 16 | For: "slider-disabled", 17 | }) 18 |
19 | @slider.Input(slider.InputProps{ 20 | ID: "slider-disabled", 21 | Value: 20, 22 | Min: -20, 23 | Max: 200, 24 | Step: 20, 25 | Disabled: true, 26 | }) 27 | } 28 |
29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/slider_external_value.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/slider" 4 | 5 | templ SliderExternalValue() { 6 |
7 |
8 | @slider.Slider() { 9 | @slider.Input(slider.InputProps{ 10 | ID: "slider-external-value", 11 | Value: 50, 12 | Min: 0, 13 | Max: 100, 14 | Step: 1, 15 | }) 16 | } 17 |
18 |
19 |

External value (linked to the slider):

20 |
21 | @slider.Value(slider.ValueProps{ 22 | For: "slider-external-value", 23 | Class: "text-3xl font-bold text-primary", 24 | }) 25 | % 26 |
27 |
28 |
29 | } 30 | -------------------------------------------------------------------------------- /internal/ui/showcase/slider_steps.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/slider" 6 | ) 7 | 8 | templ SliderSteps() { 9 |
10 | @slider.Slider(slider.Props{}) { 11 |
12 | @label.Label() { 13 | Zoom Level 14 | } 15 |
16 | @slider.Value(slider.ValueProps{ 17 | For: "slider-steps", 18 | }) 19 |
20 |
21 | @slider.Input(slider.InputProps{ 22 | ID: "slider-steps", 23 | Name: "slider-steps", 24 | Value: 100, 25 | Min: 0, 26 | Max: 200, 27 | Step: 25, 28 | }) 29 | } 30 |
31 | } 32 | -------------------------------------------------------------------------------- /internal/ui/showcase/slider_value.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/slider" 4 | 5 | templ SliderValue() { 6 |
7 | @slider.Slider() { 8 |
9 | @slider.Value(slider.ValueProps{ 10 | For: "slider-value", 11 | }) 12 |
13 | @slider.Input(slider.InputProps{ 14 | ID: "slider-value", 15 | Value: 75, 16 | Min: 0, 17 | Max: 100, 18 | Step: 1, 19 | }) 20 | } 21 |
22 | } 23 | -------------------------------------------------------------------------------- /internal/ui/showcase/spinner_colors.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/spinner" 4 | 5 | templ SpinnerColors() { 6 |
7 | @spinner.Spinner(spinner.Props{ 8 | Size: spinner.SizeMd, 9 | Color: "text-red-500", 10 | }) 11 | @spinner.Spinner(spinner.Props{ 12 | Size: spinner.SizeMd, 13 | Color: "text-green-500", 14 | }) 15 | @spinner.Spinner(spinner.Props{ 16 | Size: spinner.SizeMd, 17 | Color: "text-blue-500", 18 | }) 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/spinner_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/spinner" 4 | 5 | templ SpinnerDefault() { 6 | @spinner.Spinner(spinner.Props{ 7 | Size: spinner.SizeMd, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/spinner_in_button.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/spinner" 6 | ) 7 | 8 | templ SpinnerInButton() { 9 | @button.Button(button.Props{ 10 | Attributes: templ.Attributes{ 11 | "disabled": "true", 12 | }, 13 | }) { 14 |
15 | @spinner.Spinner(spinner.Props{ 16 | Size: spinner.SizeSm, 17 | Color: "text-primary-foreground", 18 | }) 19 | Loading 20 |
21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/ui/showcase/spinner_sizes.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/spinner" 4 | 5 | templ SpinnerSizes() { 6 |
7 | @spinner.Spinner(spinner.Props{ 8 | Size: spinner.SizeSm, 9 | }) 10 | @spinner.Spinner(spinner.Props{ 11 | Size: spinner.SizeMd, 12 | }) 13 | @spinner.Spinner(spinner.Props{ 14 | Size: spinner.SizeLg, 15 | }) 16 |
17 | } 18 | -------------------------------------------------------------------------------- /internal/ui/showcase/table.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/table" 4 | 5 | templ Table() { 6 | @table.Table() { 7 | @table.Caption() { 8 | A list of your recent hires. 9 | } 10 | @table.Header() { 11 | @table.Row() { 12 | @table.Head() { 13 | Name 14 | } 15 | @table.Head() { 16 | Role 17 | } 18 | @table.Head() { 19 | Status 20 | } 21 | @table.Head() { 22 | Actions 23 | } 24 | } 25 | } 26 | @table.Body() { 27 | @table.Row() { 28 | @table.Cell() { 29 | John Doe 30 | } 31 | @table.Cell() { 32 | Software Engineer 33 | } 34 | @table.Cell() { 35 | Active 36 | } 37 | @table.Cell() { 38 | Edit 39 | } 40 | } 41 | @table.Row() { 42 | @table.Cell() { 43 | Jane Smith 44 | } 45 | @table.Cell() { 46 | Designer 47 | } 48 | @table.Cell() { 49 | Active 50 | } 51 | @table.Cell() { 52 | Edit 53 | } 54 | } 55 | @table.Row() { 56 | @table.Cell() { 57 | Bob Johnson 58 | } 59 | @table.Cell() { 60 | Product Manager 61 | } 62 | @table.Cell() { 63 | Inactive 64 | } 65 | @table.Cell() { 66 | Edit 67 | } 68 | } 69 | @table.Footer() { 70 | @table.Row() { 71 | @table.Head() { 72 | 3 items 73 | } 74 | @table.Head() { 75 | 1 page 76 | } 77 | @table.Head() { 78 | 1-3 of 3 79 | } 80 | @table.Head() { 81 | Next 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_auto_resize.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/textarea" 4 | 5 | templ TextareaAutoResize() { 6 |
7 | @textarea.Textarea(textarea.Props{ 8 | Placeholder: "Start typing to see the magic...", 9 | AutoResize: true, 10 | }) 11 |
12 | } 13 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_custom_rows.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/textarea" 4 | 5 | templ TextareaCustomRows() { 6 |
7 | @textarea.Textarea(textarea.Props{ 8 | Placeholder: "Type your message here...", 9 | Rows: 6, 10 | }) 11 |
12 | } 13 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/textarea" 4 | 5 | templ TextareaDefault() { 6 |
7 | @textarea.Textarea(textarea.Props{ 8 | Placeholder: "Type your message here...", 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/textarea" 6 | ) 7 | 8 | templ TextareaDisabled() { 9 |
10 | @label.Label(label.Props{ 11 | For: "textarea-disabled", 12 | }) { 13 | Your Message 14 | } 15 | @textarea.Textarea(textarea.Props{ 16 | ID: "textarea-disabled", 17 | Disabled: true, 18 | Placeholder: "Type your message here...", 19 | }) 20 |
21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/textarea" 6 | ) 7 | 8 | templ TextareaForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "textarea-form", 13 | }) { 14 | Your Message 15 | } 16 | @textarea.Textarea(textarea.Props{ 17 | ID: "textarea-form", 18 | Name: "message", 19 | Placeholder: "Type your message here...", 20 | }) 21 | @form.Description() { 22 | Please type your message in the textarea. 23 | } 24 | @form.Message(form.MessageProps{ 25 | Variant: form.MessageVariantError, 26 | }) { 27 | This message is required. 28 | } 29 | } 30 |
31 | } 32 | -------------------------------------------------------------------------------- /internal/ui/showcase/textarea_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/textarea" 6 | ) 7 | 8 | templ TextareaWithLabel() { 9 |
10 | @label.Label(label.Props{ 11 | For: "textarea-with-label", 12 | }) { 13 | Your Message 14 | } 15 | @textarea.Textarea(textarea.Props{ 16 | ID: "textarea-with-label", 17 | Placeholder: "Type your message here...", 18 | Rows: 4, 19 | }) 20 |
21 | } 22 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_12hour.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/timepicker" 4 | 5 | templ TimePicker12Hour() { 6 |
7 | @timepicker.TimePicker(timepicker.Props{ 8 | Use12Hours: true, 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_custom_placeholder.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/timepicker" 4 | 5 | templ TimePickerCustomPlaceholder() { 6 |
7 | @timepicker.TimePicker(timepicker.Props{ 8 | Placeholder: "When do you want to meet?", 9 | }) 10 |
11 | } 12 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/timepicker" 4 | 5 | templ TimePickerDefault() { 6 |
7 | @timepicker.TimePicker() 8 |
9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/timepicker" 6 | ) 7 | 8 | templ TimePickerForm() { 9 |
10 | @form.Item() { 11 | @form.Label(form.LabelProps{ 12 | For: "time-picker-form", 13 | }) { 14 | Select a time 15 | } 16 | @timepicker.TimePicker(timepicker.Props{ 17 | ID: "time-picker-form", 18 | Name: "time-picker-form", 19 | HasError: true, 20 | }) 21 | @form.Description() { 22 | Select a time from the dropdown. 23 | } 24 | @form.Message(form.MessageProps{ 25 | Variant: form.MessageVariantError, 26 | }) { 27 | Please select a time 28 | } 29 | } 30 |
31 | } 32 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/timepicker" 6 | ) 7 | 8 | templ TimePickerLabel() { 9 |
10 | @label.Label(label.Props{ 11 | For: "time-picker-label", 12 | }) { 13 | Select a time 14 | } 15 | @timepicker.TimePicker(timepicker.Props{ 16 | ID: "time-picker-label", 17 | Use12Hours: true, 18 | }) 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/time_picker_selected_time.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/timepicker" 5 | "time" 6 | ) 7 | 8 | templ TimePickerSelectedTime() { 9 |
10 | @timepicker.TimePicker(timepicker.Props{ 11 | Value: time.Now(), 12 | }) 13 |
14 | } 15 | -------------------------------------------------------------------------------- /internal/ui/showcase/toast_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/button" 4 | 5 | templ ToastDefault() { 6 |
7 |
18 | @button.Button(button.Props{ 19 | Type: button.TypeSubmit, 20 | }) { 21 | Show Toast 22 | } 23 |
24 |
25 |
26 | } 27 | -------------------------------------------------------------------------------- /internal/ui/showcase/toggle_checked.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/toggle" 4 | 5 | templ ToggleChecked() { 6 | @toggle.Toggle(toggle.Props{ 7 | Checked: true, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /internal/ui/showcase/toggle_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import "github.com/axzilla/templui/internal/components/toggle" 4 | 5 | templ ToggleDefault() { 6 | @toggle.Toggle() 7 | } 8 | -------------------------------------------------------------------------------- /internal/ui/showcase/toggle_disabled.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/toggle" 6 | ) 7 | 8 | templ ToggleDisabled() { 9 |
10 | @toggle.Toggle(toggle.Props{ 11 | ID: "toggle-disabled", 12 | Disabled: true, 13 | }) 14 | @label.Label(label.Props{ 15 | For: "toggle-disabled", 16 | }) { 17 | Airplane Mode 18 | } 19 |
20 | } 21 | -------------------------------------------------------------------------------- /internal/ui/showcase/toggle_form.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/form" 5 | "github.com/axzilla/templui/internal/components/toggle" 6 | ) 7 | 8 | templ ToggleForm() { 9 |
10 | @form.Item() { 11 | @form.ItemFlex() { 12 | @toggle.Toggle(toggle.Props{ 13 | ID: "airplane-mode", 14 | Name: "airplane", 15 | }) 16 | @form.Label(form.LabelProps{ 17 | For: "airplane-mode", 18 | }) { 19 | Airplane Mode 20 | } 21 | } 22 | @form.ItemFlex() { 23 | @toggle.Toggle(toggle.Props{ 24 | ID: "wifi-mode", 25 | Name: "wifi", 26 | Disabled: true, 27 | }) 28 | @form.Label(form.LabelProps{ 29 | For: "wifi-mode", 30 | }) { 31 | Wi-Fi 32 | } 33 | } 34 | @form.ItemFlex() { 35 | @toggle.Toggle(toggle.Props{ 36 | ID: "bluetooth-mode", 37 | Name: "bluetooth", 38 | Checked: true, 39 | }) 40 | @form.Label(form.LabelProps{ 41 | For: "bluetooth-mode", 42 | }) { 43 | Bluetooth 44 | } 45 | } 46 | @form.Description() { 47 | Manage your devices connectivity options. 48 | } 49 | @form.Message(form.MessageProps{ 50 | Variant: form.MessageVariantError, 51 | }) { 52 | Please configure your connectivity settings. 53 | } 54 | } 55 |
56 | } 57 | -------------------------------------------------------------------------------- /internal/ui/showcase/toggle_with_label.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/label" 5 | "github.com/axzilla/templui/internal/components/toggle" 6 | ) 7 | 8 | templ ToggleWithLabel() { 9 |
10 | @toggle.Toggle(toggle.Props{ 11 | ID: "toggle-with-label", 12 | }) 13 | @label.Label(label.Props{ 14 | For: "toggle-with-label", 15 | }) { 16 | Airplane Mode 17 | } 18 |
19 | } 20 | -------------------------------------------------------------------------------- /internal/ui/showcase/tooltip_default.templ: -------------------------------------------------------------------------------- 1 | package showcase 2 | 3 | import ( 4 | "github.com/axzilla/templui/internal/components/button" 5 | "github.com/axzilla/templui/internal/components/tooltip" 6 | ) 7 | 8 | templ TooltipDefault() { 9 | @tooltip.Tooltip() { 10 | @tooltip.Trigger(tooltip.TriggerProps{ 11 | For: "tooltip-default", 12 | }) { 13 | @button.Button(button.Props{ 14 | Variant: button.VariantOutline, 15 | }) { 16 | Hover Me 17 | } 18 | } 19 | @tooltip.Content(tooltip.ContentProps{ 20 | ID: "tooltip-default", 21 | Position: tooltip.PositionTop, 22 | HoverDelay: 500, 23 | HoverOutDelay: 100, 24 | }) { 25 | Add to cart 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/utils/internal.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "math/rand" 8 | ) 9 | 10 | func GenerateNonce() (string, error) { 11 | nonceBytes := make([]byte, 16) 12 | _, err := rand.Read(nonceBytes) 13 | if err != nil { 14 | return "", fmt.Errorf("failed to generate nonce: %w", err) 15 | } 16 | return base64.StdEncoding.EncodeToString(nonceBytes), nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/templui.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "crypto/rand" 7 | 8 | "github.com/a-h/templ" 9 | 10 | twmerge "github.com/Oudwins/tailwind-merge-go" 11 | ) 12 | 13 | // TwMerge combines Tailwind classes and resolves conflicts. 14 | // Example: "bg-red-500 hover:bg-blue-500", "bg-green-500" → "hover:bg-blue-500 bg-green-500" 15 | func TwMerge(classes ...string) string { 16 | return twmerge.Merge(classes...) 17 | } 18 | 19 | // TwIf returns value if condition is true, otherwise an empty value of type T. 20 | // Example: true, "bg-red-500" → "bg-red-500" 21 | func If[T comparable](condition bool, value T) T { 22 | var empty T 23 | if condition { 24 | return value 25 | } 26 | return empty 27 | } 28 | 29 | // TwIfElse returns trueValue if condition is true, otherwise falseValue. 30 | // Example: true, "bg-red-500", "bg-gray-300" → "bg-red-500" 31 | func IfElse[T any](condition bool, trueValue T, falseValue T) T { 32 | if condition { 33 | return trueValue 34 | } 35 | return falseValue 36 | } 37 | 38 | // MergeAttributes combines multiple Attributes into one. 39 | // Example: MergeAttributes(attr1, attr2) → combined attributes 40 | func MergeAttributes(attrs ...templ.Attributes) templ.Attributes { 41 | merged := templ.Attributes{} 42 | for _, attr := range attrs { 43 | for k, v := range attr { 44 | merged[k] = v 45 | } 46 | } 47 | return merged 48 | } 49 | 50 | // RandomID generates a random ID string. 51 | // Example: RandomID() → "id-1a2b3c" 52 | func RandomID() string { 53 | return fmt.Sprintf("id-%s", rand.Text()) 54 | } 55 | -------------------------------------------------------------------------------- /middleware/csp.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/a-h/templ" 10 | "github.com/axzilla/templui/internal/utils" 11 | ) 12 | 13 | type CSPConfig struct { 14 | ScriptSrc []string // External script domains allowed 15 | } 16 | 17 | func WithCSP(config CSPConfig) func(http.Handler) http.Handler { 18 | return func(next http.Handler) http.Handler { 19 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | nonce, err := utils.GenerateNonce() 21 | if err != nil { 22 | log.Printf("failed to generate nonce: %v", err) 23 | w.Header().Set("Content-Security-Policy", "script-src 'self'") 24 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 25 | return 26 | } 27 | 28 | // Combine all script sources 29 | scriptSources := append( 30 | []string{"'self'", fmt.Sprintf("'nonce-%s'", nonce)}, 31 | config.ScriptSrc...) 32 | 33 | csp := fmt.Sprintf("script-src %s", strings.Join(scriptSources, " ")) 34 | w.Header().Set("Content-Security-Policy", csp) 35 | 36 | next.ServeHTTP(w, r.WithContext(templ.WithNonce(r.Context(), nonce))) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@html-eslint/eslint-plugin": "^0.37.0", 4 | "@html-eslint/parser": "^0.37.0", 5 | "eslint": "^9.24.0", 6 | "eslint-plugin-html-tailwind": "^1.2.1", 7 | "tailwindcss": "^4.1.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shiki/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as a parent image (Alpine for smaller size) 2 | FROM node:20-alpine 3 | 4 | # Set the working directory in the container 5 | WORKDIR /shiki-app 6 | 7 | # Copy package.json and package-lock.json (or yarn.lock) 8 | # This step leverages Docker layer caching 9 | COPY package*.json ./ 10 | 11 | # Install pm2 globally and then production dependencies 12 | RUN npm install -g pm2 && npm install --production 13 | 14 | # Copy the rest of the application code 15 | COPY . . 16 | 17 | # Make port 3000 available within the Docker network (not exposed to host by default) 18 | EXPOSE 3000 19 | 20 | # Define the command to run the application using pm2 21 | # -i max: Start instances based on available vCPUs 22 | # --max-memory-restart: Restart instance if it exceeds this memory limit 23 | CMD ["pm2-runtime", "server.js", "-i", "max", "--max-memory-restart", "512M"] -------------------------------------------------------------------------------- /shiki/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiki", 3 | "version": "1.0.0", 4 | "description": "Simple service to highlight code using Shiki", 5 | "main": "server.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "nodemon server.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "^4.19.2", 15 | "shiki": "^1.10.3", 16 | "nodemon": "^3.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /assets/img/social-preview.png 3 | Allow: / 4 | Disallow: /assets/ 5 | 6 | Sitemap: https://templui.io/sitemap.xml 7 | -------------------------------------------------------------------------------- /static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "embed" 4 | 5 | //go:embed * 6 | var Files embed.FS 7 | --------------------------------------------------------------------------------