From 581f9bc85783cdb2266b0e79dbce78d2f1176698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 16:45:41 -0300 Subject: [PATCH 1/2] Add docs generator and button documentation Introduce a new generator (`ruby_ui:install:docs`) that copies component documentation files from the gem to Rails applications. Documentation files follow the naming convention `{component}_docs.rb` and are copied to `app/views/docs/` with the `_docs` suffix removed. Usage: bin/rails g ruby_ui:install:docs bin/rails g ruby_ui:install:docs --force Include stub classes for docs dependencies (Views::Base, Docs::Header, Docs::VisualCodeExample, etc.) so docs files can be loaded without errors. Rails apps can override these with full implementations. Includes button component documentation as the first example. --- CONTRIBUTING.md | 18 +++ .../ruby_ui/install/docs_generator.rb | 33 ++++ lib/ruby_ui/button/button_docs.rb | 143 ++++++++++++++++++ lib/ruby_ui/docs/base.rb | 14 ++ lib/ruby_ui/docs/component_setup_tabs.rb | 15 ++ lib/ruby_ui/docs/components_table.rb | 13 ++ lib/ruby_ui/docs/header.rb | 17 +++ lib/ruby_ui/docs/visual_code_example.rb | 19 +++ test/test_helper.rb | 2 +- 9 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 lib/generators/ruby_ui/install/docs_generator.rb create mode 100644 lib/ruby_ui/button/button_docs.rb create mode 100644 lib/ruby_ui/docs/base.rb create mode 100644 lib/ruby_ui/docs/component_setup_tabs.rb create mode 100644 lib/ruby_ui/docs/components_table.rb create mode 100644 lib/ruby_ui/docs/header.rb create mode 100644 lib/ruby_ui/docs/visual_code_example.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7dba2073..de6d09d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,22 @@ While we don't have specific test coverage requirements, all contributions shoul If your changes include new components, modify how components should be used, or add new behaviors, it is highly recommended to also open a PR on the [ruby-ui/web](https://github.com/ruby-ui/web) repository. This ensures the documentation website stays up-to-date with the latest component changes. +### Installing Documentation Files + +RubyUI includes documentation files for each component that can be installed into your Rails application. These files are located at `lib/ruby_ui/{component}/{component}_docs.rb` and provide usage examples for each component. + +To install the documentation files: + +```bash +bin/rails g ruby_ui:install:docs +``` + +To overwrite existing documentation files: + +```bash +bin/rails g ruby_ui:install:docs --force +``` + +This will copy the documentation files to `app/views/docs/` in your Rails application. + Thank you for contributing to make RubyUI better! \ No newline at end of file diff --git a/lib/generators/ruby_ui/install/docs_generator.rb b/lib/generators/ruby_ui/install/docs_generator.rb new file mode 100644 index 00000000..5ec67333 --- /dev/null +++ b/lib/generators/ruby_ui/install/docs_generator.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "rails/generators" + +module RubyUI + module Generators + module Install + class DocsGenerator < Rails::Generators::Base + namespace "ruby_ui:install:docs" + source_root File.expand_path("../../../ruby_ui", __dir__) + class_option :force, type: :boolean, default: false + + def copy_docs_files + say "Installing RubyUI documentation files..." + + docs_file_paths.each do |source_path| + dest_filename = File.basename(source_path).sub("_docs", "") + copy_file source_path, Rails.root.join("app/views/docs", dest_filename), force: options["force"] + end + + say "" + say "Documentation installed to app/views/docs/", :green + end + + private + + def docs_file_paths + Dir.glob(File.join(self.class.source_root, "*", "*_docs.rb")) + end + end + end + end +end diff --git a/lib/ruby_ui/button/button_docs.rb b/lib/ruby_ui/button/button_docs.rb new file mode 100644 index 00000000..ee943b12 --- /dev/null +++ b/lib/ruby_ui/button/button_docs.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +class Views::Docs::Button < Views::Base + def view_template + component = "Button" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Button", description: "Displays a button or a component that looks like a button.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Button { "Button" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Primary", context: self) do + <<~RUBY + Button(variant: :primary) { "Primary" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Secondary", context: self) do + <<~RUBY + Button(variant: :secondary) { "Secondary" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Destructive", context: self) do + <<~RUBY + Button(variant: :destructive) { "Destructive" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Outline", context: self) do + <<~RUBY + Button(variant: :outline) { "Outline" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Ghost", context: self) do + <<~RUBY + Button(variant: :ghost) { "Ghost" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Link", context: self) do + <<~RUBY + Button(variant: :link) { "Link" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + Button(disabled: true) { "Disabled" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + Button(aria: {disabled: "true"}) { "Aria Disabled" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Icon", context: self) do + <<~RUBY + Button(variant: :outline, icon: true) do + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 20 20", + fill: "currentColor", + class: "w-5 h-5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z", + clip_rule: "evenodd" + ) + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With Icon", context: self) do + <<~RUBY + Button(variant: :primary) do + svg( + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + class: "w-4 h-4 mr-2" + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" + ) + end + span { "Login with Email" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With Icon", context: self) do + <<~RUBY + Button(variant: :primary, disabled: true) do + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 20 20", + fill: "currentColor", + class: "w-4 h-4 mr-2 animate-spin" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z", + clip_rule: "evenodd" + ) + end + span { "Please wait" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Submit", context: self) do + <<~RUBY + Button(variant: :primary, type: :submit) do + span { "Submit application" } + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/docs/base.rb b/lib/ruby_ui/docs/base.rb new file mode 100644 index 00000000..b3acbff9 --- /dev/null +++ b/lib/ruby_ui/docs/base.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Views + class Base < Phlex::HTML + def Heading(level:, &) + tag = :"h#{level}" + send(tag, &) + end + + def component_files(component_name) + [] + end + end +end diff --git a/lib/ruby_ui/docs/component_setup_tabs.rb b/lib/ruby_ui/docs/component_setup_tabs.rb new file mode 100644 index 00000000..4f48449e --- /dev/null +++ b/lib/ruby_ui/docs/component_setup_tabs.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Components + module ComponentSetup + class Tabs < Phlex::HTML + def initialize(component_name:) + @component_name = component_name + end + + def view_template + # Minimal stub - empty by default + end + end + end +end diff --git a/lib/ruby_ui/docs/components_table.rb b/lib/ruby_ui/docs/components_table.rb new file mode 100644 index 00000000..4738a144 --- /dev/null +++ b/lib/ruby_ui/docs/components_table.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Docs + class ComponentsTable < Phlex::HTML + def initialize(files) + @files = files + end + + def view_template + # Minimal stub - empty by default + end + end +end diff --git a/lib/ruby_ui/docs/header.rb b/lib/ruby_ui/docs/header.rb new file mode 100644 index 00000000..4624a796 --- /dev/null +++ b/lib/ruby_ui/docs/header.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Docs + class Header < Phlex::HTML + def initialize(title:, description: nil) + @title = title + @description = description + end + + def view_template + div do + h1 { @title } + p { @description } if @description + end + end + end +end diff --git a/lib/ruby_ui/docs/visual_code_example.rb b/lib/ruby_ui/docs/visual_code_example.rb new file mode 100644 index 00000000..0007c3f3 --- /dev/null +++ b/lib/ruby_ui/docs/visual_code_example.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Docs + class VisualCodeExample < Phlex::HTML + def initialize(title:, context:) + @title = title + @context = context + end + + def view_template(&block) + code = block.call + div do + h3 { @title } + pre { code } + @context.instance_eval(code) + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 7a5f8fc3..d31728d7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,7 +10,7 @@ module RubyUI extend Phlex::Kit - Dir.glob("lib/ruby_ui/**/*.rb").map do |path| + Dir.glob("lib/ruby_ui/**/*.rb").reject { |f| f.include?("/docs/") || f.end_with?("_docs.rb") }.map do |path| class_name = path.split("/").last.delete_suffix(".rb").split("_").map(&:capitalize).join.to_sym autoload class_name, path From 9118639acdc3e3aa6a3a4d123c254b0c7005523d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Thu, 11 Dec 2025 14:33:24 -0300 Subject: [PATCH 2/2] Add documentation for batch 1 components (#336) Add docs files for: - accordion - alert - alert_dialog - aspect_ratio - avatar - badge - breadcrumb - calendar - card --- lib/ruby_ui/accordion/accordion_docs.rb | 53 +++++++ lib/ruby_ui/alert/alert_docs.rb | 135 ++++++++++++++++++ lib/ruby_ui/alert_dialog/alert_dialog_docs.rb | 35 +++++ lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb | 64 +++++++++ lib/ruby_ui/avatar/avatar_docs.rb | 92 ++++++++++++ lib/ruby_ui/badge/badge_docs.rb | 80 +++++++++++ lib/ruby_ui/breadcrumb/breadcrumb_docs.rb | 116 +++++++++++++++ lib/ruby_ui/calendar/calendar_docs.rb | 34 +++++ lib/ruby_ui/card/card_docs.rb | 114 +++++++++++++++ 9 files changed, 723 insertions(+) create mode 100644 lib/ruby_ui/accordion/accordion_docs.rb create mode 100644 lib/ruby_ui/alert/alert_docs.rb create mode 100644 lib/ruby_ui/alert_dialog/alert_dialog_docs.rb create mode 100644 lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb create mode 100644 lib/ruby_ui/avatar/avatar_docs.rb create mode 100644 lib/ruby_ui/badge/badge_docs.rb create mode 100644 lib/ruby_ui/breadcrumb/breadcrumb_docs.rb create mode 100644 lib/ruby_ui/calendar/calendar_docs.rb create mode 100644 lib/ruby_ui/card/card_docs.rb diff --git a/lib/ruby_ui/accordion/accordion_docs.rb b/lib/ruby_ui/accordion/accordion_docs.rb new file mode 100644 index 00000000..7f755822 --- /dev/null +++ b/lib/ruby_ui/accordion/accordion_docs.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class Views::Docs::Accordion < Views::Base + def view_template + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + component = "Accordion" + render Docs::Header.new(title: component, + description: "A vertically stacked set of interactive headings that each reveal a section of content.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + @@code = <<~RUBY + div(class: "w-full") do + Accordion do + AccordionItem do + AccordionTrigger do + p(class: "font-medium") { "What is PhlexUI?" } + AccordionIcon() + end + + AccordionContent do + p(class: "text-sm pb-4") do + "PhlexUI is a UI component library for Ruby devs who want to build better, faster." + end + end + end + end + + Accordion do + AccordionItem do + AccordionTrigger do + p(class: "font-medium") { "Can I use it with Rails?" } + AccordionIcon() + end + + AccordionContent do + p(class: "text-sm pb-4") do + "Yes, PhlexUI is pure Ruby and works great with Rails. It's a Ruby gem that you can install into your Rails app." + end + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/alert/alert_docs.rb b/lib/ruby_ui/alert/alert_docs.rb new file mode 100644 index 00000000..5211074c --- /dev/null +++ b/lib/ruby_ui/alert/alert_docs.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +class Views::Docs::Alert < Views::Base + def view_template + component = "Alert" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Alert", description: "Displays a callout for user attention.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Alert do + rocket_icon + AlertTitle { "Pro tip" } + AlertDescription { "With RubyUI you'll ship faster." } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Without Icon", context: self) do + <<~RUBY + Alert do + AlertTitle { "Pro tip" } + AlertDescription { "Simply, don't include an icon and your alert will look like this." } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Warning", context: self) do + <<~RUBY + Alert(variant: :warning) do + info_icon + AlertTitle { "Ship often" } + AlertDescription { "Shipping is good, your users will thank you for it." } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Destructive", context: self) do + <<~RUBY + Alert(variant: :destructive) do + alert_icon + AlertTitle { "Oopsie daisy!" } + AlertDescription { "Your design system is non-existent." } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Success", context: self) do + <<~RUBY + Alert(variant: :success) do + check_icon + AlertTitle { "Installation successful" } + AlertDescription { "You're all set to start using RubyUI in your application." } + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def rocket_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-5 h-5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M9.315 7.584C12.195 3.883 16.695 1.5 21.75 1.5a.75.75 0 01.75.75c0 5.056-2.383 9.555-6.084 12.436A6.75 6.75 0 019.75 22.5a.75.75 0 01-.75-.75v-4.131A15.838 15.838 0 016.382 15H2.25a.75.75 0 01-.75-.75 6.75 6.75 0 017.815-6.666zM15 6.75a2.25 2.25 0 100 4.5 2.25 2.25 0 000-4.5z", + clip_rule: "evenodd" + ) + s.path( + d: + "M5.26 17.242a.75.75 0 10-.897-1.203 5.243 5.243 0 00-2.05 5.022.75.75 0 00.625.627 5.243 5.243 0 005.022-2.051.75.75 0 10-1.202-.897 3.744 3.744 0 01-3.008 1.51c0-1.23.592-2.323 1.51-3.008z" + ) + end + end + + def alert_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-5 h-5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z", + clip_rule: "evenodd" + ) + end + end + + def info_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-5 h-5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z", + clip_rule: "evenodd" + ) + end + end + + def check_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-5 h-5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm13.36-1.814a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z", + clip_rule: "evenodd" + ) + end + end +end diff --git a/lib/ruby_ui/alert_dialog/alert_dialog_docs.rb b/lib/ruby_ui/alert_dialog/alert_dialog_docs.rb new file mode 100644 index 00000000..43f17654 --- /dev/null +++ b/lib/ruby_ui/alert_dialog/alert_dialog_docs.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class Views::Docs::AlertDialog < Views::Base + def view_template + component = "AlertDialog" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Alert Dialog", description: "A modal dialog that interrupts the user with important content and expects a response.") + + Heading(level: 2) { "Usage" } + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + AlertDialog do + AlertDialogTrigger do + Button { "Show dialog" } + end + AlertDialogContent do + AlertDialogHeader do + AlertDialogTitle { "Are you absolutely sure?" } + AlertDialogDescription { "This action cannot be undone. This will permanently delete your account and remove your data from our servers." } + end + AlertDialogFooter do + AlertDialogCancel { "Cancel" } + AlertDialogAction { "Continue" } # Will probably be a link to a controller action (e.g. delete account) + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb b/lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb new file mode 100644 index 00000000..760a713f --- /dev/null +++ b/lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +class Views::Docs::AspectRatio < Views::Base + def view_template + component = "AspectRatio" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Aspect Ratio", description: "Displays content within a desired ratio.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "16/9", context: self) do + <<~RUBY + AspectRatio(aspect_ratio: "16/9", class: "rounded-md overflow-hidden border shadow-sm") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path('pattern.jpg') + ) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "4/3", context: self) do + <<~RUBY + AspectRatio(aspect_ratio: "4/3", class: "rounded-md overflow-hidden border shadow-sm") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path('pattern.jpg') + ) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "1/1", context: self) do + <<~RUBY + AspectRatio(aspect_ratio: "1/1", class: "rounded-md overflow-hidden border shadow-sm") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path('pattern.jpg') + ) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "21/9", context: self) do + <<~RUBY + AspectRatio(aspect_ratio: "21/9", class: "rounded-md overflow-hidden border shadow-sm") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path('pattern.jpg') + ) + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/avatar/avatar_docs.rb b/lib/ruby_ui/avatar/avatar_docs.rb new file mode 100644 index 00000000..4f5e4c35 --- /dev/null +++ b/lib/ruby_ui/avatar/avatar_docs.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +class Views::Docs::Avatar < Views::Base + def view_template + component = "Avatar" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Avatar", description: "An image element with a fallback for representing the user.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Image & fallback", context: self) do + <<~RUBY + Avatar do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Only fallback", context: self) do + <<~RUBY + Avatar do + AvatarFallback { "JD" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Sizes", context: self) do + <<~RUBY + div(class: 'flex items-center space-x-2') do + # size: :xs + Avatar(size: :xs) do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + # size: :sm + Avatar(size: :sm) do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + # size: :md + Avatar(size: :md) do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + # size: :lg + Avatar(size: :lg) do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + # size: :xl + Avatar(size: :xl) do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Sizes (only fallback)", context: self) do + @@code = <<~RUBY + div(class: 'flex items-center space-x-2') do + # size: :xs + Avatar(size: :xs) do + AvatarFallback { "JD" } + end + # size: :sm + Avatar(size: :sm) do + AvatarFallback { "JD" } + end + # size: :md + Avatar(size: :md) do + AvatarFallback { "JD" } + end + # size: :lg + Avatar(size: :lg) do + AvatarFallback { "JD" } + end + # size: :xl + Avatar(size: :xl) do + AvatarFallback { "JD" } + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/badge/badge_docs.rb b/lib/ruby_ui/badge/badge_docs.rb new file mode 100644 index 00000000..9687663a --- /dev/null +++ b/lib/ruby_ui/badge/badge_docs.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +class Views::Docs::Badge < Views::Base + def view_template + component = "Badge" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Badge", description: "Displays a badge or a component that looks like a badge.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Default", context: self) do + <<~RUBY + Badge { "Badge" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Primary", context: self) do + <<~RUBY + Badge(variant: :primary) { 'Primary' } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Outline", context: self) do + <<~RUBY + Badge(variant: :outline) { 'Outline' } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Variants", context: self) do + <<~RUBY + div(class: 'flex flex-wrap gap-2 justify-center') do + Badge(variant: :destructive) { 'Destructive' } + Badge(variant: :warning) { 'Warning' } + Badge(variant: :success) { 'Success' } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Other Colors", context: self) do + <<~RUBY + div(class: 'flex flex-wrap gap-2 justify-center') do + Badge(variant: :red) { 'Red' } + Badge(variant: :orange) { 'Orange' } + Badge(variant: :amber) { 'Amber' } + Badge(variant: :yellow) { 'Yellow' } + Badge(variant: :lime) { 'Lime' } + Badge(variant: :green) { 'Green' } + Badge(variant: :emerald) { 'Emerald' } + Badge(variant: :teal) { 'Teal' } + Badge(variant: :cyan) { 'Cyan' } + Badge(variant: :sky) { 'Sky' } + Badge(variant: :blue) { 'Blue' } + Badge(variant: :indigo) { 'Indigo' } + Badge(variant: :violet) { 'Violet' } + Badge(variant: :purple) { 'Purple' } + Badge(variant: :fuchsia) { 'Fuchsia' } + Badge(variant: :pink) { 'Pink' } + Badge(variant: :rose) { 'Rose' } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Sizes", context: self) do + <<~RUBY + div(class: 'flex flex-wrap gap-2 justify-center items-center') do + Badge(size: :sm) { "Small" } + Badge(size: :md) { "Medium" } + Badge(size: :lg) { "Large" } + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + # components + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/breadcrumb/breadcrumb_docs.rb b/lib/ruby_ui/breadcrumb/breadcrumb_docs.rb new file mode 100644 index 00000000..127014d2 --- /dev/null +++ b/lib/ruby_ui/breadcrumb/breadcrumb_docs.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +class Views::Docs::Breadcrumb < Views::Base + def view_template + component = "Breadcrumb" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Breadcrumb", description: "Indicates the user's current location within a navigational hierarchy.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Breadcrumb do + BreadcrumbList do + BreadcrumbItem do + BreadcrumbLink(href: "/") { "Home" } + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbLink(href: "/docs/accordion") { "Components" } + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbPage { "Breadcrumb" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With custom separator", context: self) do + <<~RUBY + Breadcrumb do + BreadcrumbList do + BreadcrumbItem do + BreadcrumbLink(href: "/") { "Home" } + end + BreadcrumbSeparator { slash_icon } + BreadcrumbItem do + BreadcrumbLink(href: "/docs/accordion") { "Components" } + end + BreadcrumbSeparator { slash_icon } + BreadcrumbItem do + BreadcrumbPage { "Breadcrumb" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Collapsed", context: self) do + <<~RUBY + Breadcrumb do + BreadcrumbList do + BreadcrumbItem do + BreadcrumbLink(href: "/") { "Home" } + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbEllipsis() + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbLink(href: "/docs/accordion") { "Components" } + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbPage { "Breadcrumb" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With Link component", context: self) do + <<~RUBY + Breadcrumb do + BreadcrumbList do + BreadcrumbItem do + BreadcrumbLink(href: "/") { "Home" } + end + BreadcrumbSeparator() + BreadcrumbItem do + Link(href: "/docs/accordion", class: "px-0") { "Components" } + end + BreadcrumbSeparator() + BreadcrumbItem do + BreadcrumbPage { "Breadcrumb" } + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def slash_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + class: "w-4 h-4", + viewbox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round" + ) { |s| s.path(d: "M22 2 2 22") } + end +end diff --git a/lib/ruby_ui/calendar/calendar_docs.rb b/lib/ruby_ui/calendar/calendar_docs.rb new file mode 100644 index 00000000..c8f9a519 --- /dev/null +++ b/lib/ruby_ui/calendar/calendar_docs.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Views::Docs::Calendar < Views::Base + def view_template + component = "Calendar" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Calendar", description: "A date field component that allows users to enter and edit date.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Connect to input", context: self) do + <<~RUBY + div(class: 'space-y-4') do + Input(type: 'string', placeholder: "Select a date", class: 'rounded-md border shadow', id: 'date', data_controller: 'ruby-ui--calendar-input') + Calendar(input_id: '#date', class: 'rounded-md border shadow') + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Format date", description: "Format dates with date-fns", context: self) do + <<~RUBY + div(class: 'space-y-4') do + Input(type: 'string', placeholder: "Select a date", class: 'rounded-md border shadow', id: 'formatted-date', data_controller: 'ruby-ui--calendar-input') + Calendar(input_id: '#formatted-date', date_format: 'PPPP', class: 'rounded-md border shadow') + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/card/card_docs.rb b/lib/ruby_ui/card/card_docs.rb new file mode 100644 index 00000000..b0ac30df --- /dev/null +++ b/lib/ruby_ui/card/card_docs.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +class Views::Docs::Card < Views::Base + def view_template + component = "Card" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Card", description: "Displays a card with header, content, and footer.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Card with image", context: self) do + <<~RUBY + Card(class: 'w-96') do + CardHeader do + CardTitle { 'You might like "RubyUI"' } + CardDescription { "@joeldrapper" } + end + CardContent do + AspectRatio(aspect_ratio: "16/9", class: "rounded-md overflow-hidden border") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_url('pattern.jpg') + ) + end + end + CardFooter(class: 'flex justify-end gap-x-2') do + Button(variant: :outline) { "See more" } + Button(variant: :primary) { "Buy now" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Card with full-width image", context: self) do + <<~RUBY + Card(class: 'w-96 overflow-hidden') do + AspectRatio(aspect_ratio: "16/9", class: "border-b") do + img( + alt: "Placeholder", + loading: "lazy", + src: image_url('pattern.jpg') + ) + end + CardHeader do + CardTitle { 'Introducing RubyUI' } + CardDescription { "Kickstart your project today!" } + end + CardFooter(class: 'flex justify-end') do + Button(variant: :outline) { "Get started" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Account balance", context: self) do + <<~RUBY + Card(class: 'w-96 overflow-hidden') do + CardHeader do + div(class: 'w-10 h-10 rounded-xl flex items-center justify-center bg-violet-100 text-violet-700 -rotate-6') do + cash_icon + end + end + CardContent(class: 'space-y-1') do + CardDescription(class: 'font-medium') { "Current Balance" } + h5(class: 'font-semibold text-4xl') { '$2,602' } + end + CardFooter do + Text(size: "2", class: "text-muted-foreground") { "**** 4620" } + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + def arrow_icon(classes: nil) + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 20 20", + fill: "currentColor", + class: ["w-4 h-4", classes] + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M3 10a.75.75 0 01.75-.75h10.638L10.23 5.29a.75.75 0 111.04-1.08l5.5 5.25a.75.75 0 010 1.08l-5.5 5.25a.75.75 0 11-1.04-1.08l4.158-3.96H3.75A.75.75 0 013 10z", + clip_rule: "evenodd" + ) + end + end + + def cash_icon(classes: nil) + svg( + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + class: ["w-6 h-6", classes] + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z" + ) + end + end +end