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/7] 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 bd78494747f80a3c01ade3474cae6fe3f1d52c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 17:35:10 -0300 Subject: [PATCH 2/7] Add documentation for batch 1 components 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 From e4aca5bb3e2294020e50d67e93f705a207ef6662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 17:43:35 -0300 Subject: [PATCH 3/7] Add documentation for batch 2 components Add documentation files for: carousel, chart, checkbox, clipboard, codeblock, collapsible, combobox, command, context_menu, dialog --- lib/ruby_ui/carousel/carousel_docs.rb | 104 ++++++++++++ lib/ruby_ui/chart/chart_docs.rb | 115 +++++++++++++ lib/ruby_ui/checkbox/checkbox_docs.rb | 41 +++++ lib/ruby_ui/clipboard/clipboard_docs.rb | 30 ++++ lib/ruby_ui/codeblock/codeblock_docs.rb | 55 +++++++ lib/ruby_ui/collapsible/collapsible_docs.rb | 96 +++++++++++ lib/ruby_ui/combobox/combobox_docs.rb | 151 +++++++++++++++++ lib/ruby_ui/command/command_docs.rb | 154 ++++++++++++++++++ lib/ruby_ui/context_menu/context_menu_docs.rb | 85 ++++++++++ lib/ruby_ui/dialog/dialog_docs.rb | 102 ++++++++++++ 10 files changed, 933 insertions(+) create mode 100644 lib/ruby_ui/carousel/carousel_docs.rb create mode 100644 lib/ruby_ui/chart/chart_docs.rb create mode 100644 lib/ruby_ui/checkbox/checkbox_docs.rb create mode 100644 lib/ruby_ui/clipboard/clipboard_docs.rb create mode 100644 lib/ruby_ui/codeblock/codeblock_docs.rb create mode 100644 lib/ruby_ui/collapsible/collapsible_docs.rb create mode 100644 lib/ruby_ui/combobox/combobox_docs.rb create mode 100644 lib/ruby_ui/command/command_docs.rb create mode 100644 lib/ruby_ui/context_menu/context_menu_docs.rb create mode 100644 lib/ruby_ui/dialog/dialog_docs.rb diff --git a/lib/ruby_ui/carousel/carousel_docs.rb b/lib/ruby_ui/carousel/carousel_docs.rb new file mode 100644 index 00000000..7a11402d --- /dev/null +++ b/lib/ruby_ui/carousel/carousel_docs.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +class Views::Docs::Carousel < Views::Base + def view_template + component = "Carousel" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Carousel", description: "A carousel with motion and swipe built using Embla.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Carousel(options: {loop:false}, class: "w-full max-w-xs") do + CarouselContent do + 5.times do |index| + CarouselItem do + div(class: "p-1") do + Card do + CardContent(class: "flex aspect-square items-center justify-center p-6") do + span(class: "text-4xl font-semibold") { index + 1 } + end + end + end + end + end + end + CarouselPrevious() + CarouselNext() + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Sizes", context: self) do + <<~RUBY + Carousel(class: "w-full max-w-sm") do + CarouselContent do + 5.times do |index| + CarouselItem(class: "md:basis-1/2 lg:basis-1/3") do + div(class: "p-1") do + Card do + CardContent(class: "flex aspect-square items-center justify-center p-6") do + span(class: "text-3xl font-semibold") { index + 1 } + end + end + end + end + end + end + CarouselPrevious() + CarouselNext() + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Spacing", context: self) do + <<~RUBY + Carousel(class: "w-full max-w-sm") do + CarouselContent(class: "-ml-1") do + 5.times do |index| + CarouselItem(class: "pl-1 md:basis-1/2 lg:basis-1/3") do + div(class: "p-1") do + Card do + CardContent(class: "flex aspect-square items-center justify-center p-6") do + span(class: "text-2xl font-semibold") { index + 1 } + end + end + end + end + end + end + CarouselPrevious() + CarouselNext() + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Orientation", context: self) do + <<~RUBY + Carousel(orientation: :vertical, options: {align: "start"}, class: "w-full max-w-xs") do + CarouselContent(class: "-mt-1 h-[200px]") do + 5.times do |index| + CarouselItem(class: "pt-1 md:basis-1/2") do + div(class: "p-1") do + Card do + CardContent(class: "flex items-center justify-center p-6") do + span(class: "text-3xl font-semibold") { index + 1 } + end + end + end + end + end + end + CarouselPrevious() + CarouselNext() + 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/chart/chart_docs.rb b/lib/ruby_ui/chart/chart_docs.rb new file mode 100644 index 00000000..6aaf88ed --- /dev/null +++ b/lib/ruby_ui/chart/chart_docs.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +class Views::Docs::Chart < Views::Base + def view_template + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Chart", description: "Displays information in a visual way.") + + Heading(level: 2) { "Introduction" } + + Text do + plain "RubyUI uses " + InlineLink(href: "https://www.chartjs.org/") { "Chart.js" } + plain " to render charts. Chart.js is a free open-source JavaScript library for data visualization, which supports 8 chart types: bar, line, area, pie, bubble, radar, polar, and scatter. If you're unfamiliar with Chart.js. We recommend the " + InlineLink(href: "https://www.chartjs.org/docs/latest/getting-started/") { "Getting Started guide" } + plain ". " + end + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Bar Chart", context: self) do + <<~RUBY + options = { + type: 'bar', + data: { + labels: ['Phlex', 'VC', 'ERB'], + datasets: [{ + label: 'render time (ms)', + data: [100, 520, 1200], + }] + }, + options: { + indexAxis: 'y', + scales: { + y: { + beginAtZero: true + } + }, + }, + } + + Chart(options: options) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Line Graph", context: self) do + <<~RUBY + options = { + type: 'line', + data: { + labels: ['Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], + datasets: [{ + label: 'Github Stars', + data: [40, 30, 79, 140, 290, 550], + }] + }, + options: { + scales: { + y: { + beginAtZero: true + } + }, + plugins: { + legend: { + display: false + } + } + }, + } + + Chart(options: options) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Pie Chart", description: "Setting custom background color", context: self) do + <<~RUBY + options = { + type: 'pie', + data: { + labels: [ + 'Red', + 'Blue', + 'Yellow' + ], + datasets: [{ + label: 'My First Dataset', + data: [300, 50, 100], + backgroundColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 205, 86)' + ], + hoverOffset: 4 + }] + }, + } + + Chart(options: options) + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: "Chart") + + render Docs::ComponentsTable.new(components) + end + end + + private + + def components + [ + ::Docs::ComponentStruct.new(name: "ChartController", source: "https://github.com/PhlexUI/phlex_ui_stimulus/blob/main/controllers/chart_controller.js", built_using: :stimulus), + ::Docs::ComponentStruct.new(name: "Chart", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/chart.rb", built_using: :phlex) + ] + end +end diff --git a/lib/ruby_ui/checkbox/checkbox_docs.rb b/lib/ruby_ui/checkbox/checkbox_docs.rb new file mode 100644 index 00000000..941ccc1c --- /dev/null +++ b/lib/ruby_ui/checkbox/checkbox_docs.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class Views::Docs::Checkbox < Views::Base + def view_template + component = "Checkbox" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Checkbox", description: "A control that allows the user to toggle between checked and not checked.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + div(class: 'flex items-center space-x-3') do + Checkbox(id: 'terms') + label(for: 'terms', class: 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70') { "Accept terms and conditions" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Checked", context: self) do + <<~RUBY + div(class: "items-top flex space-x-3") do + Checkbox(id: 'terms1', checked: true) + div(class: "grid gap-1.5 leading-none") do + label( + for: "terms1", + class: + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" + ) { " Accept terms and conditions " } + p(class: "text-sm text-muted-foreground") { " You agree to our Terms of Service and Privacy Policy." } + 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/clipboard/clipboard_docs.rb b/lib/ruby_ui/clipboard/clipboard_docs.rb new file mode 100644 index 00000000..c6075d06 --- /dev/null +++ b/lib/ruby_ui/clipboard/clipboard_docs.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Views::Docs::Clipboard < Views::Base + def view_template + component = "Clipboard" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Clipboard", description: "A control to allow you to copy content to the clipboard.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Clipboard(success: "Copied!", error: "Copy failed!", class: "relative", options: {placement: "top"}) do + ClipboardSource(class: "hidden") { span { "Born rich!!!" } } + + ClipboardTrigger do + Link(href: "#", class: "gap-1") do + Text(size: :small, class: "text-primary") { "Copy the secret of success!!!" } + 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/codeblock/codeblock_docs.rb b/lib/ruby_ui/codeblock/codeblock_docs.rb new file mode 100644 index 00000000..23f1be61 --- /dev/null +++ b/lib/ruby_ui/codeblock/codeblock_docs.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class Views::Docs::Codeblock < Views::Base + def view_template + component = "Codeblock" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Codeblock", description: "A component for displaying highlighted code.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "With clipboard", context: self) do + <<~RUBY + code = <<~CODE + def hello_world + puts "Hello, world!" + end + CODE + div(class: 'w-full') do + Codeblock(code, syntax: :ruby) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Without clipboard", context: self) do + <<~RUBY + code = <<~CODE + def hello_world + puts "Hello, world!" + end + CODE + div(class: 'w-full') do + Codeblock(code, syntax: :ruby, clipboard: false) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Custom message", description: "Copy the code to see the message", context: self) do + <<~RUBY + code = <<~CODE + def hello_world + puts "Hello, world!" + end + CODE + div(class: 'w-full') do + Codeblock(code, syntax: :ruby, clipboard_success: "Nice one!") + 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/collapsible/collapsible_docs.rb b/lib/ruby_ui/collapsible/collapsible_docs.rb new file mode 100644 index 00000000..04654686 --- /dev/null +++ b/lib/ruby_ui/collapsible/collapsible_docs.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +class Views::Docs::Collapsible < Views::Base + def view_template + component = "Collapsible" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Collapsible", description: "An interactive component which expands/collapses a panel.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Collapsible do + div(class: "flex items-center justify-between space-x-4 px-4 py-2") do + h4(class: "text-sm font-semibold") { " @joeldrapper starred 3 repositories" } + CollapsibleTrigger do + Button(variant: :ghost, icon: true) do + chevron_icon + span(class: "sr-only") { "Toggle" } + end + end + end + + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "phlex-ruby/phlex" + end + + CollapsibleContent do + div(class: 'space-y-2 mt-2') do + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "phlex-ruby/phlex-rails" + end + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "ruby-ui/ruby_ui" + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Open", context: self) do + <<~RUBY + Collapsible(open: true) do + div(class: "flex items-center justify-between space-x-4 px-4 py-2") do + h4(class: "text-sm font-semibold") { " @joeldrapper starred 3 repositories" } + CollapsibleTrigger do + Button(variant: :ghost, icon: true) do + chevron_icon + span(class: "sr-only") { "Toggle" } + end + end + end + + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "phlex-ruby/phlex" + end + + CollapsibleContent do + div(class: 'space-y-2 mt-2') do + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "phlex-ruby/phlex-rails" + end + div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do + "ruby-ui/ruby_ui" + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def chevron_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 20 20", + fill: "currentColor", + class: "w-4 h-4" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z", + clip_rule: "evenodd" + ) + end + end +end diff --git a/lib/ruby_ui/combobox/combobox_docs.rb b/lib/ruby_ui/combobox/combobox_docs.rb new file mode 100644 index 00000000..2fd1d422 --- /dev/null +++ b/lib/ruby_ui/combobox/combobox_docs.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +class Views::Docs::Combobox < Views::Base + @@code_example = nil + + def view_template + component = "Combobox" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: component, description: "Autocomplete input and command palette with a list of suggestions.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Single option", context: self) do + <<~RUBY + div class: "w-96" do + Combobox do + ComboboxTrigger placeholder: "Pick value" + + ComboboxPopover do + ComboboxSearchInput(placeholder: "Pick value or type anything") + + ComboboxList do + ComboboxEmptyState { "No result" } + + ComboboxListGroup(label: "Fruits") do + ComboboxItem do + ComboboxRadio(name: "food", value: "apple") + span { "Apple" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "banana") + span { "Banana" } + end + end + + ComboboxListGroup(label: "Vegetable") do + ComboboxItem do + ComboboxRadio(name: "food", value: "brocoli") + span { "Broccoli" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "carrot") + span { "Carrot" } + end + end + + ComboboxListGroup(label: "Others") do + ComboboxItem do + ComboboxRadio(name: "food", value: "chocolate") + span { "Chocolate" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "milk") + span { "Milk" } + end + end + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Multiple options", context: self) do + <<~RUBY + div class: "w-96" do + Combobox term: "things" do + ComboboxTrigger placeholder: "Pick value" + + ComboboxPopover do + ComboboxSearchInput(placeholder: "Pick value or type anything") + + ComboboxList do + ComboboxEmptyState { "No result" } + + ComboboxItem(class: "mt-3") do + ComboboxToggleAllCheckbox(name: "all", value: "all") + span { "Select all" } + end + + ComboboxListGroup label: "Fruits" do + ComboboxItem do + ComboboxCheckbox(name: "food", value: "apple") + span { "Apple" } + end + + ComboboxItem do + ComboboxCheckbox(name: "food", value: "banana") + span { "Banana" } + end + end + + ComboboxListGroup label: "Vegetable" do + ComboboxItem do + ComboboxCheckbox(name: "food", value: "brocoli") + span { "Broccoli" } + end + + ComboboxItem do + ComboboxCheckbox(name: "food", value: "carrot") + span { "Carrot" } + end + end + + ComboboxListGroup label: "Others" do + ComboboxItem do + ComboboxCheckbox(name: "food", value: "chocolate") + span { "Chocolate" } + end + + ComboboxItem do + ComboboxCheckbox(name: "food", value: "milk") + span { "Milk" } + end + end + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + div(class: "w-96") do + Combobox do + ComboboxTrigger(disabled: true, placeholder: "Pick value") + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + div(class: "w-96") do + Combobox do + ComboboxTrigger(aria: {disabled: "true"}, placeholder: "Pick value") + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: "Combobox") + + render Docs::ComponentsTable.new(component_files("Combobox")) + end + end +end diff --git a/lib/ruby_ui/command/command_docs.rb b/lib/ruby_ui/command/command_docs.rb new file mode 100644 index 00000000..37ce24cb --- /dev/null +++ b/lib/ruby_ui/command/command_docs.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +class Views::Docs::Command < Views::Base + def view_template + component = "Command" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Command", description: "Fast, composable, unstyled command menu for Phlex.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + CommandDialog do + CommandDialogTrigger do + Button(variant: "outline", class: 'w-56 pr-2 pl-3 justify-between') do + div(class: "flex items-center space-x-1") do + search_icon + span(class: "text-muted-foreground font-normal") do + plain "Search" + end + end + ShortcutKey do + span(class: "text-xs") { "⌘" } + plain "K" + end + end + end + CommandDialogContent do + Command do + CommandInput(placeholder: "Type a command or search...") + CommandEmpty { "No results found." } + CommandList do + CommandGroup(title: "Components") do + components_list.each do |component| + CommandItem(value: component[:name], href: component[:path]) do + default_icon + plain component[:name] + end + end + end + CommandGroup(title: "Settings") do + settings_list.each do |setting| + CommandItem(value: setting[:name], href: setting[:path]) do + default_icon + plain setting[:name] + end + end + end + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With keybinding", context: self) do + <<~RUBY + CommandDialog do + CommandDialogTrigger(keybindings: ['keydown.ctrl+j@window', 'keydown.meta+j@window']) do + p(class: "text-sm text-muted-foreground") do + span(class: 'mr-1') { "Press" } + ShortcutKey do + span(class: "text-xs") { "⌘" } + plain "J" + end + end + end + CommandDialogContent do + Command do + CommandInput(placeholder: "Type a command or search...") + CommandEmpty { "No results found." } + CommandList do + CommandGroup(title: "Components") do + components_list.each do |component| + CommandItem(value: component[:name], href: component[:path]) do + default_icon + plain component[:name] + end + end + end + CommandGroup(title: "Settings") do + settings_list.each do |setting| + CommandItem(value: setting[:name], href: setting[:path]) do + default_icon + plain setting[:name] + end + end + end + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def search_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 20 20", + fill: "currentColor", + class: "w-4 h-4 mr-1.5" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", + clip_rule: "evenodd" + ) + end + end + + def default_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: + "M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm4.28 10.28a.75.75 0 000-1.06l-3-3a.75.75 0 10-1.06 1.06l1.72 1.72H8.25a.75.75 0 000 1.5h5.69l-1.72 1.72a.75.75 0 101.06 1.06l3-3z", + clip_rule: "evenodd" + ) + end + end + + def components_list + [ + {name: "Accordion", path: docs_accordion_path}, + {name: "Alert", path: docs_alert_path}, + {name: "Alert Dialog", path: docs_alert_dialog_path}, + {name: "Aspect Ratio", path: docs_aspect_ratio_path}, + {name: "Avatar", path: docs_avatar_path}, + {name: "Badge", path: docs_badge_path} + ] + end + + def settings_list + [ + {name: "Profile", path: "#"}, + {name: "Mail", path: "#"}, + {name: "Settings", path: "#"} + ] + end +end diff --git a/lib/ruby_ui/context_menu/context_menu_docs.rb b/lib/ruby_ui/context_menu/context_menu_docs.rb new file mode 100644 index 00000000..5043e21d --- /dev/null +++ b/lib/ruby_ui/context_menu/context_menu_docs.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +class Views::Docs::ContextMenu < Views::Base + def view_template + component = "ContextMenu" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Context Menu", description: "Displays a menu to the user — such as a set of actions or functions — triggered by a right click.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + ContextMenu do + ContextMenuTrigger(class: 'flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') { "Right click here" } + ContextMenuContent(class: 'w-64') do + ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" } + ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" } + ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" } + ContextMenuSeparator + ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" } + ContextMenuItem(href: '#') { "Show Full URLs" } + ContextMenuSeparator + ContextMenuLabel(inset: true) { "More Tools" } + ContextMenuSeparator + ContextMenuItem(href: '#') { "Developer Tools" } + ContextMenuItem(href: '#') { "Task Manager" } + ContextMenuItem(href: '#') { "Extensions" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Placement", context: self) do + <<~RUBY + div(class: 'space-y-4') do + ContextMenu(options: { placement: 'right' }) do + ContextMenuTrigger(class: 'flex flex-col items-center gap-y-2 h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') do + plain "Right click here" + Badge(variant: :primary) { "right" } + end + ContextMenuContent(class: 'w-64') do + ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" } + ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" } + ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" } + ContextMenuSeparator + ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" } + ContextMenuItem(href: '#') { "Show Full URLs" } + ContextMenuSeparator + ContextMenuLabel(inset: true) { "More Tools" } + ContextMenuSeparator + ContextMenuItem(href: '#') { "Developer Tools" } + ContextMenuItem(href: '#') { "Task Manager" } + ContextMenuItem(href: '#') { "Extensions" } + end + end + ContextMenu(options: { placement: 'left' }) do + ContextMenuTrigger(class: 'flex flex-col items-center gap-y-2 h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') do + plain "Right click here" + Badge(variant: :primary) { "left" } + end + ContextMenuContent(class: 'w-64') do + ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" } + ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" } + ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" } + ContextMenuSeparator + ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" } + ContextMenuItem(href: '#') { "Show Full URLs" } + ContextMenuSeparator + ContextMenuLabel(inset: true) { "More Tools" } + ContextMenuSeparator + ContextMenuItem(href: '#') { "Developer Tools" } + ContextMenuItem(href: '#') { "Task Manager" } + ContextMenuItem(href: '#') { "Extensions" } + 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/dialog/dialog_docs.rb b/lib/ruby_ui/dialog/dialog_docs.rb new file mode 100644 index 00000000..9876cdd6 --- /dev/null +++ b/lib/ruby_ui/dialog/dialog_docs.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +class Views::Docs::Dialog < Views::Base + def view_template + component = "Dialog" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Dialog", description: "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Dialog do + DialogTrigger do + Button { "Open Dialog" } + end + DialogContent do + DialogHeader do + DialogTitle { "RubyUI to the rescue" } + DialogDescription { "RubyUI helps you build accessible standard compliant web apps with ease" } + end + DialogMiddle do + AspectRatio(aspect_ratio: "16/9", class: 'rounded-md overflow-hidden border') do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path("pattern.jpg") + ) + end + end + DialogFooter do + Button(variant: :outline, data: { action: 'click->ruby-ui--dialog#dismiss' }) { "Cancel" } + Button { "Save" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Size", description: "Applicable for wider screens", context: self) do + <<~RUBY + div(class: 'flex flex-wrap justify-center gap-2') do + Dialog do + DialogTrigger do + Button { "Small Dialog" } + end + DialogContent(size: :sm) do + DialogHeader do + DialogTitle { "RubyUI to the rescue" } + DialogDescription { "RubyUI helps you build accessible standard compliant web apps with ease" } + end + DialogMiddle do + AspectRatio(aspect_ratio: "16/9", class: 'rounded-md overflow-hidden border') do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path("pattern.jpg") + ) + end + end + DialogFooter do + Button(variant: :outline, data: { action: 'click->ruby-ui--dialog#dismiss' }) { "Cancel" } + Button { "Save" } + end + end + end + + Dialog do + DialogTrigger do + Button { "Large Dialog" } + end + DialogContent(size: :lg) do + DialogHeader do + DialogTitle { "RubyUI to the rescue" } + DialogDescription { "RubyUI helps you build accessible standard compliant web apps with ease" } + end + DialogMiddle do + AspectRatio(aspect_ratio: "16/9", class: 'rounded-md overflow-hidden border') do + img( + alt: "Placeholder", + loading: "lazy", + src: image_path("pattern.jpg") + ) + end + end + DialogFooter do + Button(variant: :outline, data: { action: 'click->ruby-ui--dialog#dismiss' }) { "Cancel" } + Button { "Save" } + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end From a02202c24ca5437de9aa2a752ec4b219172d8ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 17:52:33 -0300 Subject: [PATCH 4/7] Add documentation for batch 3 components Migrate documentation files for dropdown_menu, form, hover_card, input, link, masked_input, and pagination components. --- .../dropdown_menu/dropdown_menu_docs.rb | 212 ++++++++++++++++++ lib/ruby_ui/form/form_docs.rb | 178 +++++++++++++++ lib/ruby_ui/hover_card/hover_card_docs.rb | 71 ++++++ lib/ruby_ui/input/input_docs.rb | 68 ++++++ lib/ruby_ui/link/link_docs.rb | 106 +++++++++ lib/ruby_ui/masked_input/masked_input_docs.rb | 47 ++++ lib/ruby_ui/pagination/pagination_docs.rb | 127 +++++++++++ 7 files changed, 809 insertions(+) create mode 100644 lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb create mode 100644 lib/ruby_ui/form/form_docs.rb create mode 100644 lib/ruby_ui/hover_card/hover_card_docs.rb create mode 100644 lib/ruby_ui/input/input_docs.rb create mode 100644 lib/ruby_ui/link/link_docs.rb create mode 100644 lib/ruby_ui/masked_input/masked_input_docs.rb create mode 100644 lib/ruby_ui/pagination/pagination_docs.rb diff --git a/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb b/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb new file mode 100644 index 00000000..a57469e2 --- /dev/null +++ b/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +class Views::Docs::DropdownMenu < Views::Base + def view_template + component = "DropdownMenu" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Dropdown Menu", description: "Displays a menu to the user — such as a set of actions or functions — triggered by a button.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + DropdownMenu do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline) { "Open" } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Placement", description: "If the DropdownMenu conflicts with edge, it will auto-adjust it's placement", context: self) do + <<~RUBY + div(class: 'grid grid-cols-1 sm:grid-cols-3 gap-4') do + # -- TOP -- + DropdownMenu(options: { placement: 'top' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'top-start' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top-start' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'top-end' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top-end' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + # -- BOTTOM -- + DropdownMenu(options: { placement: 'bottom' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'bottom-start' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom-start' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'bottom-end' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom-end' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + # -- LEFT -- + DropdownMenu(options: { placement: 'left' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'left-start' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left-start' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'left-end' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left-end' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + # -- RIGHT -- + DropdownMenu(options: { placement: 'right' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'right-start' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right-start' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + end + end + + DropdownMenu(options: { placement: 'right-end' }) do + DropdownMenuTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right-end' } + end + DropdownMenuContent do + DropdownMenuLabel { "My Account" } + DropdownMenuSeparator + DropdownMenuItem(href: '#') { "Profile" } + DropdownMenuItem(href: '#') { "Billing" } + DropdownMenuItem(href: '#') { "Team" } + DropdownMenuItem(href: '#') { "Subscription" } + 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/form/form_docs.rb b/lib/ruby_ui/form/form_docs.rb new file mode 100644 index 00000000..499ad4b6 --- /dev/null +++ b/lib/ruby_ui/form/form_docs.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +class Views::Docs::Form < Views::Base + def view_template + component = "Form" + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Form", description: "Building forms with built-in client-side validations.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + FormFieldLabel { "Default error" } + Input(placeholder: "Joel Drapper", required: true, minlength: "3") { "Joel Drapper" } + FormFieldHint() + FormFieldError() + end + Button(type: "submit") { "Save" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + FormField do + FormFieldLabel { "Disabled" } + Input(disabled: true, placeholder: "Joel Drapper", required: true, minlength: "3") { "Joel Drapper" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + FormField do + FormFieldLabel { "Aria Disabled" } + Input(aria: {disabled: "true"}, placeholder: "Joel Drapper", required: true, minlength: "3") { "Joel Drapper" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Custom error message", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + FormFieldLabel { "Custom error message" } + Input(placeholder: "joel@drapper.me", required: true, data_value_missing: "Custom error message") + FormFieldError() + end + Button(type: "submit") { "Save" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Backend error", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + FormFieldLabel { "Backend error" } + Input(placeholder: "Joel Drapper", required: true) + FormFieldError { "Error from backend" } + end + Button(type: "submit") { "Save" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Checkbox", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + Checkbox(required: true) + label( + class: + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" + ) { " Accept terms and conditions " } + FormFieldError() + end + Button(type: "submit") { "Save" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Select", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + FormFieldLabel { "Select" } + Select do + SelectInput(required: true) + SelectTrigger do + SelectValue(placeholder: "Select a fruit") + end + SelectContent() do + SelectGroup do + SelectLabel { "Fruits" } + SelectItem(value: "apple") { "Apple" } + SelectItem(value: "orange") { "Orange" } + SelectItem(value: "banana") { "Banana" } + SelectItem(value: "watermelon") { "Watermelon" } + end + end + end + FormFieldError() + end + Button(type: "submit") { "Save" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Combobox", context: self) do + <<~RUBY + Form(class: "w-2/3 space-y-6") do + FormField do + FormFieldLabel { "Combobox" } + + Combobox do + ComboboxTrigger placeholder: "Pick value" + + ComboboxPopover do + ComboboxSearchInput(placeholder: "Pick value or type anything") + + ComboboxList do + ComboboxEmptyState { "No result" } + + ComboboxListGroup label: "Fruits" do + ComboboxItem do + ComboboxRadio(name: "food", value: "apple", required: true) + span { "Apple" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "banana", required: true) + span { "Banana" } + end + end + + ComboboxListGroup label: "Vegetable" do + ComboboxItem do + ComboboxRadio(name: "food", value: "brocoli", required: true) + span { "Broccoli" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "carrot", required: true) + span { "Carrot" } + end + end + + ComboboxListGroup label: "Others" do + ComboboxItem do + ComboboxRadio(name: "food", value: "chocolate", required: true) + span { "Chocolate" } + end + + ComboboxItem do + ComboboxRadio(name: "food", value: "milk", required: true) + span { "Milk" } + end + end + end + end + end + + FormFieldError() + end + Button(type: "submit") { "Save" } + 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/hover_card/hover_card_docs.rb b/lib/ruby_ui/hover_card/hover_card_docs.rb new file mode 100644 index 00000000..1aa18ed5 --- /dev/null +++ b/lib/ruby_ui/hover_card/hover_card_docs.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class Views::Docs::HoverCard < Views::Base + def view_template + component = "HoverCard" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Hover Card", description: "For sighted users to preview content available behind a link.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + HoverCard do + HoverCardTrigger do + Button(variant: :link) { "@joeldrapper" } # Make this a link in order to navigate somewhere + end + HoverCardContent do + div(class: "flex justify-between space-x-4") do + Avatar do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + div(class: "space-y-1") do + h4(class: "text-sm font-medium") { "@joeldrapper" } + p(class: "text-sm") do + "Creator of Phlex Components. Ruby on Rails developer." + end + div(class: "flex items-center pt-2") do + svg( + width: "15", + height: "15", + viewbox: "0 0 15 15", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + class: "mr-2 h-4 w-4 opacity-70" + ) do |s| + s.path( + d: + "M4.5 1C4.77614 1 5 1.22386 5 1.5V2H10V1.5C10 1.22386 10.2239 1 10.5 1C10.7761 1 11 1.22386 11 1.5V2H12.5C13.3284 2 14 2.67157 14 3.5V12.5C14 13.3284 13.3284 14 12.5 14H2.5C1.67157 14 1 13.3284 1 12.5V3.5C1 2.67157 1.67157 2 2.5 2H4V1.5C4 1.22386 4.22386 1 4.5 1ZM10 3V3.5C10 3.77614 10.2239 4 10.5 4C10.7761 4 11 3.77614 11 3.5V3H12.5C12.7761 3 13 3.22386 13 3.5V5H2V3.5C2 3.22386 2.22386 3 2.5 3H4V3.5C4 3.77614 4.22386 4 4.5 4C4.77614 4 5 3.77614 5 3.5V3H10ZM2 6V12.5C2 12.7761 2.22386 13 2.5 13H12.5C12.7761 13 13 12.7761 13 12.5V6H2ZM7 7.5C7 7.22386 7.22386 7 7.5 7C7.77614 7 8 7.22386 8 7.5C8 7.77614 7.77614 8 7.5 8C7.22386 8 7 7.77614 7 7.5ZM9.5 7C9.22386 7 9 7.22386 9 7.5C9 7.77614 9.22386 8 9.5 8C9.77614 8 10 7.77614 10 7.5C10 7.22386 9.77614 7 9.5 7ZM11 7.5C11 7.22386 11.2239 7 11.5 7C11.7761 7 12 7.22386 12 7.5C12 7.77614 11.7761 8 11.5 8C11.2239 8 11 7.77614 11 7.5ZM11.5 9C11.2239 9 11 9.22386 11 9.5C11 9.77614 11.2239 10 11.5 10C11.7761 10 12 9.77614 12 9.5C12 9.22386 11.7761 9 11.5 9ZM9 9.5C9 9.22386 9.22386 9 9.5 9C9.77614 9 10 9.22386 10 9.5C10 9.77614 9.77614 10 9.5 10C9.22386 10 9 9.77614 9 9.5ZM7.5 9C7.22386 9 7 9.22386 7 9.5C7 9.77614 7.22386 10 7.5 10C7.77614 10 8 9.77614 8 9.5C8 9.22386 7.77614 9 7.5 9ZM5 9.5C5 9.22386 5.22386 9 5.5 9C5.77614 9 6 9.22386 6 9.5C6 9.77614 5.77614 10 5.5 10C5.22386 10 5 9.77614 5 9.5ZM3.5 9C3.22386 9 3 9.22386 3 9.5C3 9.77614 3.22386 10 3.5 10C3.77614 10 4 9.77614 4 9.5C4 9.22386 3.77614 9 3.5 9ZM3 11.5C3 11.2239 3.22386 11 3.5 11C3.77614 11 4 11.2239 4 11.5C4 11.7761 3.77614 12 3.5 12C3.22386 12 3 11.7761 3 11.5ZM5.5 11C5.22386 11 5 11.2239 5 11.5C5 11.7761 5.22386 12 5.5 12C5.77614 12 6 11.7761 6 11.5C6 11.2239 5.77614 11 5.5 11ZM7 11.5C7 11.2239 7.22386 11 7.5 11C7.77614 11 8 11.2239 8 11.5C8 11.7761 7.77614 12 7.5 12C7.22386 12 7 11.7761 7 11.5ZM9.5 11C9.22386 11 9 11.2239 9 11.5C9 11.7761 9.22386 12 9.5 12C9.77614 12 10 11.7761 10 11.5C10 11.2239 9.77614 11 9.5 11Z", + fill: "currentColor", + fill_rule: "evenodd", + clip_rule: "evenodd" + ) + end + span(class: "text-xs text-muted-foreground") { "Joined December 2021" } + end + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + # def components + # [ + # Docs::ComponentStruct.new(name: "PopoverController", source: "https://github.com/PhlexUI/phlex_ui_stimulus/blob/main/controllers/popover_controller.js", built_using: :stimulus), + # Docs::ComponentStruct.new(name: "HoverCard", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/hover_card.rb", built_using: :phlex), + # Docs::ComponentStruct.new(name: "HoverCardTrigger", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/hover_card/trigger.rb", built_using: :phlex), + # Docs::ComponentStruct.new(name: "HoverCardContent", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/hover_card/content.rb", built_using: :phlex) + # ] + # end +end diff --git a/lib/ruby_ui/input/input_docs.rb b/lib/ruby_ui/input/input_docs.rb new file mode 100644 index 00000000..460247bc --- /dev/null +++ b/lib/ruby_ui/input/input_docs.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +class Views::Docs::Input < Views::Base + def view_template + component = "Input" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Input", description: "Displays a form input field or a component that looks like an input field.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Email", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + Input(type: "email", placeholder: "Email") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "File", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + label(for: "picture") { "Picture" } + Input(type: "file", id: "picture") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + Input(disabled: true, type: "email", placeholder: "Email") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + Input(aria: {disabled: "true"}, type: "email", placeholder: "Email") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With label", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + label(for: "email1") { "Email" } + Input(type: "email", placeholder: "Email", id: "email1") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With button", context: self) do + <<~RUBY + div(class: 'flex w-full max-w-sm items-center space-x-2') do + Input(type: "email", placeholder: "Email") + Button { "Subscribe" } + 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/link/link_docs.rb b/lib/ruby_ui/link/link_docs.rb new file mode 100644 index 00000000..3a17a3a8 --- /dev/null +++ b/lib/ruby_ui/link/link_docs.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +class Views::Docs::Link < Views::Base + def view_template + component = "Link" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Link", description: "Displays a link that looks like a button or underline link.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", description: "This is the default appearance of a Link", context: self) do + <<~RUBY + Link(href: "#") { "Link" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + Link(aria: {disabled: "true"}, href: "#") { "Link" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Primary", description: "This is the primary variant of a Link", context: self) do + <<~RUBY + Link(href: "#", variant: :primary) { "Primary" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Secondary", description: "This is the secondary variant of a Link", context: self) do + <<~RUBY + Link(href: "#", variant: :secondary) { "Secondary" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Destructive", description: "This is the destructive variant of a Link", context: self) do + <<~RUBY + Link(href: "#", variant: :destructive) { "Destructive" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Icon", description: "This is the icon variant of a Link", context: self) do + <<~RUBY + Link(href: "#", variant: :outline, icon: true) do + chevron_icon + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With Icon", description: "This is the primary variant of a Link with an icon", context: self) do + <<~RUBY + Link(href: "#", variant: :primary) do + email_icon + span { "Login with Email" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Ghost", description: "This is the ghost variant of a Link", context: self) do + <<~RUBY + Link(href: "#", variant: :ghost) { "Ghost" } + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def chevron_icon + 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 + + def email_icon + 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 + end +end diff --git a/lib/ruby_ui/masked_input/masked_input_docs.rb b/lib/ruby_ui/masked_input/masked_input_docs.rb new file mode 100644 index 00000000..25ea4986 --- /dev/null +++ b/lib/ruby_ui/masked_input/masked_input_docs.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Views::Docs::MaskedInput < Views::Base + def view_template + component = "MaskedInput" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "MaskedInput", description: "Displays a form input field with applied mask.") + + Heading(level: 2) { "Usage" } + + Text do + plain "For advanced usage, check out the " + InlineLink(href: "https://beholdr.github.io/maska/v3", target: "_blank") { "Maska website" } + plain "." + end + + render Docs::VisualCodeExample.new(title: "Phone number", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + MaskedInput(data: {maska: "(##) #####-####"}) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Hex color code", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + MaskedInput(data: {maska: "!#HHHHHH", maska_tokens: "H:[0-9a-fA-F]"}) + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "CPF / CNPJ", context: self) do + <<~RUBY + div(class: 'grid w-full max-w-sm items-center gap-1.5') do + MaskedInput(data: {maska: "['###.###.###-##', '##.###.###/####-##']"}) + 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/pagination/pagination_docs.rb b/lib/ruby_ui/pagination/pagination_docs.rb new file mode 100644 index 00000000..b7987383 --- /dev/null +++ b/lib/ruby_ui/pagination/pagination_docs.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +class Views::Docs::Pagination < Views::Base + def view_template + component = "Pagination" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Pagination", description: "Pagination with page navigation, next and previous links.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", description: "This is the default appearance of a Pagination", context: self) do + <<~RUBY + Pagination do + PaginationContent do + PaginationItem(href: "#") do + chevrons_left_icon + plain "First" + end + PaginationItem(href: "#") do + chevron_left_icon + plain "Prev" + end + + PaginationEllipsis + + PaginationItem(href: "#") { "4" } + PaginationItem(href: "#", active: true) { "5" } + PaginationItem(href: "#") { "6" } + + PaginationEllipsis + + PaginationItem(href: "#") do + plain "Next" + chevron_right_icon + end + PaginationItem(href: "#") do + plain "Last" + chevrons_right_icon + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def chevrons_left_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + fill: "none", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "h-4 w-4" + ) do |s| + s.path(stroke: "none", d: "M0 0h24v24H0z", fill: "none") + s.path(d: "M11 7l-5 5l5 5") + s.path(d: "M17 7l-5 5l5 5") + end + end + + def chevron_left_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + fill: "none", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "h-4 w-4" + ) do |s| + s.path(stroke: "none", d: "M0 0h24v24H0z", fill: "none") + s.path(d: "M15 6l-6 6l6 6") + end + end + + def chevrons_right_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + fill: "none", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "h-4 w-4" + ) do |s| + s.path(stroke: "none", d: "M0 0h24v24H0z", fill: "none") + s.path(d: "M7 7l5 5l-5 5") + s.path(d: "M13 7l5 5l-5 5") + end + end + + def chevron_right_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewbox: "0 0 24 24", + stroke_width: "1.5", + stroke: "currentColor", + fill: "none", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "h-4 w-4" + ) do |s| + s.path(stroke: "none", d: "M0 0h24v24H0z", fill: "none") + s.path(d: "M9 6l6 6l-6 6") + end + end +end From a9a3be155f79e8546f0650f297283145f424e910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 18:02:58 -0300 Subject: [PATCH 5/7] Add documentation for batch 4 components --- lib/ruby_ui/popover/popover_docs.rb | 971 ++++++++++++++++++ lib/ruby_ui/progress/progress_docs.rb | 27 + lib/ruby_ui/radio_button/radio_button_docs.rb | 53 + lib/ruby_ui/select/select_docs.rb | 129 +++ lib/ruby_ui/separator/separator_docs.rb | 36 + lib/ruby_ui/sheet/sheet_docs.rb | 76 ++ lib/ruby_ui/shortcut_key/shortcut_key_docs.rb | 29 + lib/ruby_ui/sidebar/sidebar_docs.rb | 176 ++++ 8 files changed, 1497 insertions(+) create mode 100644 lib/ruby_ui/popover/popover_docs.rb create mode 100644 lib/ruby_ui/progress/progress_docs.rb create mode 100644 lib/ruby_ui/radio_button/radio_button_docs.rb create mode 100644 lib/ruby_ui/select/select_docs.rb create mode 100644 lib/ruby_ui/separator/separator_docs.rb create mode 100644 lib/ruby_ui/sheet/sheet_docs.rb create mode 100644 lib/ruby_ui/shortcut_key/shortcut_key_docs.rb create mode 100644 lib/ruby_ui/sidebar/sidebar_docs.rb diff --git a/lib/ruby_ui/popover/popover_docs.rb b/lib/ruby_ui/popover/popover_docs.rb new file mode 100644 index 00000000..1046dc25 --- /dev/null +++ b/lib/ruby_ui/popover/popover_docs.rb @@ -0,0 +1,971 @@ +# frozen_string_literal: true + +class Views::Docs::Popover < Views::Base + def view_template + component = "Popover" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Popover", description: "Displays rich content in a portal, triggered by a button.") + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Popover do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline) { "Open Popover" } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Placement", context: self) do + <<~RUBY + div(class: 'grid grid-cols-1 sm:grid-cols-3 gap-4') do + # -- TOP -- + Popover(options: { placement: 'top' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'top-start' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top-start' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'top-end' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'top-end' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + # -- RIGHT -- + Popover(options: { placement: 'right' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'right-start' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right-start' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'right-end' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'right-end' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + # -- LEFT -- + Popover(options: { placement: 'left' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'left-start' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left-start' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'left-end' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'left-end' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + # -- BOTTOM -- + Popover(options: { placement: 'bottom' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'bottom-start' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom-start' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + + Popover(options: { placement: 'bottom-end' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline, class: 'w-full justify-center') { 'bottom-end' } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Trigger", context: self) do + <<~RUBY + Popover(options: { trigger: 'click' }) do + PopoverTrigger(class: 'w-full') do + Button(variant: :outline) { "Click" } + end + PopoverContent(class: 'w-40') do + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Profile" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" + ) + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" + ) + end + plain "Settings" + end + Link(href: "#", variant: :ghost, class: 'w-full justify-start pl-2') 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: + "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" + ) + end + plain "Logout" + 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/progress/progress_docs.rb b/lib/ruby_ui/progress/progress_docs.rb new file mode 100644 index 00000000..dc2a7c47 --- /dev/null +++ b/lib/ruby_ui/progress/progress_docs.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Views::Docs::Progress < Views::Base + def view_template + component = "Progress" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Progress", description: "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.") + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Progress(value: 50, class: "w-[60%]") + RUBY + end + + render Docs::VisualCodeExample.new(title: "With custom indicator color", context: self) do + <<~RUBY + Progress(value: 35, class: "w-[60%] [&>*]:bg-success") + 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/radio_button/radio_button_docs.rb b/lib/ruby_ui/radio_button/radio_button_docs.rb new file mode 100644 index 00000000..15d0e024 --- /dev/null +++ b/lib/ruby_ui/radio_button/radio_button_docs.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class Views::Docs::RadioButton < Views::Base + def view_template + component = "RadioButton" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Radio Button", description: "A control that allows users to make a single selection from a list of options.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + div(class: "flex items-center space-x-2") do + RadioButton(id: "default") + FormFieldLabel(for: "default") { "Default" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Checked", context: self) do + <<~RUBY + div(class: "flex items-center space-x-2") do + RadioButton(id: "checked", checked: true) + FormFieldLabel(for: "checked") { "Checked" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + div(class: "flex flex-row items-center gap-2") do + RadioButton(class: "peer",id: "disabled", disabled: true) + FormFieldLabel(for: "disabled") { "Disabled" } + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + div(class: "flex flex-row items-center gap-2") do + RadioButton(class: "peer", id: "aria-disabled", aria: {disabled: "true"}) + FormFieldLabel(for: "aria-disabled") { "Aria Disabled" } + 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/select/select_docs.rb b/lib/ruby_ui/select/select_docs.rb new file mode 100644 index 00000000..0209b22f --- /dev/null +++ b/lib/ruby_ui/select/select_docs.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +class Views::Docs::Select < Views::Base + def view_template + component = "Select" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Select", description: "Displays a list of options for the user to pick from—triggered by a button.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Select (Deconstructed)", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "apple", id: "select-a-fruit") + + SelectTrigger do + SelectValue(placeholder: "Select a fruit", id: "select-a-fruit") { "Apple" } + end + + SelectContent(outlet_id: "select-a-fruit") do + SelectGroup do + SelectLabel { "Fruits" } + SelectItem(value: "apple") { "Apple" } + SelectItem(value: "orange") { "Orange" } + SelectItem(value: "banana") { "Banana" } + SelectItem(value: "watermelon") { "Watermelon" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Pre-selected Item", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "banana", id: "select-preselected-fruit") + + SelectTrigger do + SelectValue(placeholder: "Select a fruit", id: "select-preselected-fruit") { "Banana" } + end + + SelectContent(outlet_id: "select-preselected-fruit") do + SelectGroup do + SelectLabel { "Fruits" } + SelectItem(value: "apple") { "Apple" } + SelectItem(value: "orange") { "Orange" } + SelectItem(value: "banana") { "Banana" } + SelectItem(value: "watermelon") { "Watermelon" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "apple", id: "select-a-fruit") + + SelectTrigger(disabled: true) do + SelectValue(placeholder: "Select a fruit", id: "select-a-fruit") { "Apple" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Data Disabled", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "apple", id: "select-a-fruit") + + SelectTrigger do + SelectValue(placeholder: "Select a fruit", id: "select-a-fruit") { "Apple" } + end + + SelectContent(outlet_id: "select-a-fruit") do + SelectGroup do + SelectLabel { "Fruits" } + SelectItem(data: {disabled: true}, value: "apple") { "Apple" } + SelectItem(value: "orange") { "Orange" } + SelectItem(value: "banana") { "Banana" } + SelectItem(data: {disabled: true}, value: "watermelon") { "Watermelon" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled Trigger", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "apple", id: "select-a-fruit") + + SelectTrigger(aria: {disabled: "true"}) do + SelectValue(placeholder: "Select a fruit", id: "select-a-fruit") { "Apple" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled Item", context: self) do + <<~RUBY + Select(class: "w-56") do + SelectInput(value: "apple", id: "select-a-fruit") + + SelectTrigger do + SelectValue(placeholder: "Select a fruit", id: "select-a-fruit") { "Apple" } + end + + SelectContent(outlet_id: "select-a-fruit") do + SelectGroup do + SelectLabel { "Fruits" } + SelectItem(aria: {disabled: "true"}, value: "apple") { "Apple" } + SelectItem(value: "orange") { "Orange" } + SelectItem(value: "banana") { "Banana" } + SelectItem(aria: {disabled: "true"}, value: "watermelon") { "Watermelon" } + 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/separator/separator_docs.rb b/lib/ruby_ui/separator/separator_docs.rb new file mode 100644 index 00000000..c5d980d6 --- /dev/null +++ b/lib/ruby_ui/separator/separator_docs.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class Views::Docs::Separator < Views::Base + def view_template + component = "Separator" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Separator", description: "Visually or semantically separates content.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + div do + div(class: "space-y-1") do + h4(class: "text-sm font-medium leading-none") { "RubyUI" } + p(class: "text-sm text-muted-foreground") { "An open-source UI component library." } + end + Separator(class: "my-4") + div(class: "flex h-5 items-center space-x-4 text-sm") do + div { "Blog" } + Separator(as: :hr, orientation: :vertical) + div { "Docs" } + Separator(orientation: :vertical) + div { "Source" } + 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/sheet/sheet_docs.rb b/lib/ruby_ui/sheet/sheet_docs.rb new file mode 100644 index 00000000..8865c0a2 --- /dev/null +++ b/lib/ruby_ui/sheet/sheet_docs.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +class Views::Docs::Sheet < Views::Base + def view_template + component = "Sheet" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Sheet", description: "Extends the Sheet component to display content that complements the main content of the screen.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Sheet do + SheetTrigger do + Button(variant: :outline) { "Open Sheet" } + end + SheetContent(class: 'sm:max-w-sm') do + SheetHeader do + SheetTitle { "Edit profile" } + SheetDescription { "Make changes to your profile here. Click save when you're done." } + end + + SheetMiddle do + label { "Name" } + Input(placeholder: "Joel Drapper") { "Joel Drapper" } + label { "Email" } + Input(placeholder: "joel@drapper.me") + end + SheetFooter do + Button(variant: :outline, data: { action: 'click->ruby-ui--sheet-content#close' }) { "Cancel" } + Button(type: "submit") { "Save" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Side", description: "Use the side property to indicate the edge of the screen where the component will appear.", context: self) do + <<~RUBY + div(class: 'grid grid-cols-2 gap-4') do + # -- TOP -- + Sheet do + SheetTrigger do + Button(variant: :outline, class: 'w-full justify-center') { :top } + end + SheetContent(side: :top, class: ("sm:max-w-sm" if [:left, :right].include?(:top))) do + SheetHeader do + SheetTitle { "Edit profile" } + SheetDescription { "Make changes to your profile here. Click save when you're done." } + end + Form do + SheetMiddle do + label { "Name" } + Input(placeholder: "Joel Drapper") { "Joel Drapper" } + + label { "Email" } + Input(placeholder: "joel@drapper.me") + end + SheetFooter do + Button(variant: :outline, data: { action: 'click->ruby-ui--sheet-content#close' }) { "Cancel" } + Button(type: "submit") { "Save" } + 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/shortcut_key/shortcut_key_docs.rb b/lib/ruby_ui/shortcut_key/shortcut_key_docs.rb new file mode 100644 index 00000000..ea1ee43e --- /dev/null +++ b/lib/ruby_ui/shortcut_key/shortcut_key_docs.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Views::Docs::ShortcutKey < Views::Base + def view_template + component = "ShortcutKey" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Shortcut Key", description: "A component for displaying keyboard shortcuts.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + div(class: "flex flex-col items-center gap-y-4") do + ShortcutKey do + span(class: "text-xs") { "⌘" } + plain "K" + end + p(class: "text-muted-foreground text-sm text-center") { "Note this does not trigger anything, it is purely a visual prompt" } + 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/sidebar/sidebar_docs.rb b/lib/ruby_ui/sidebar/sidebar_docs.rb new file mode 100644 index 00000000..165872fa --- /dev/null +++ b/lib/ruby_ui/sidebar/sidebar_docs.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +class Views::Docs::Sidebar < Views::Base + def view_template + component = "Sidebar" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Sidebar", description: "A composable, themeable and customizable sidebar component.") + + Heading(level: 2) { "Usage" } + + Alert do + info_icon + AlertTitle { "Requirements" } + AlertDescription { "The sidebar component depends on the following components:" } + ul(class: "list-disc list-inside") do + li do + InlineLink(href: docs_sheet_path, target: "_blank", class: "inline-flex items-center gap-2") do + span { "Sheet" } + external_icon_link + end + end + li do + div(class: "inline-flex items-center gap-2") do + InlineLink(href: docs_separator_path, target: "_blank") { "Separator" } + external_icon_link + end + end + end + end + + render Docs::VisualCodeExample.new(title: "Example", src: "/docs/sidebar/example", context: self) do + Views::Docs::Sidebar::Example::CODE + end + + render Docs::VisualCodeExample.new(title: "Inset variant", src: "/docs/sidebar/inset", context: self) do + Views::Docs::Sidebar::InsetExample::CODE + end + + render Docs::VisualCodeExample.new(title: "Dialog variant", context: self) do + <<~RUBY + Dialog(data: {action: "ruby-ui--dialog:connect->ruby-ui--dialog#open"}) do + DialogTrigger do + Button { "Open Dialog" } + end + DialogContent(class: "grid overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]") do + SidebarWrapper(class: "items-start") do + Sidebar(collapsible: :none, class: "hidden md:flex") do + SidebarContent do + SidebarGroup do + SidebarGroupContent do + SidebarMenu do + SidebarMenuItem do + SidebarMenuButton(as: :a, href: "#") do + search_icon() + span { "Search" } + end + end + SidebarMenuItem do + SidebarMenuButton(as: :a, href: "#", active: true) do + home_icon() + span { "Home" } + end + end + SidebarMenuItem do + SidebarMenuButton(as: :a, href: "#") do + inbox_icon() + span { "Inbox" } + end + end + end + end + end + end + end + main(class: "flex h-[480px] flex-1 flex-col overflow-hidden") do + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + def search_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "lucide lucide-search" + ) do |s| + s.circle(cx: "11", cy: "11", r: "8") + s.path(d: "M21 21L16.7 16.7") + end + end + + def home_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "lucide lucide-house" + ) do |s| + s.path(d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8") + s.path(d: "M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z") + end + end + + def inbox_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "lucide lucide-inbox" + ) do |s| + s.polyline(points: "22 12 16 12 14 15 10 15 8 12 2 12") + s.path(d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z") + end + end + + def external_icon_link + svg( + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round", + class: "lucide lucide-external-link-icon lucide-external-link size-3" + ) do |s| + s.path(d: "M15 3h6v6") + s.path(d: "M10 14 21 3") + s.path(d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6") + 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 +end From b329b5fb884495ab7ccbb46505079c6a32928675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Wed, 10 Dec 2025 18:13:49 -0300 Subject: [PATCH 6/7] Add missing stub classes and methods for documentation - Update VisualCodeExample to accept description and src parameters - Add InlineLink, Text, Alert, AlertTitle, AlertDescription helpers - Add route helper stubs (docs_*_path methods) - Add sidebar example constants --- lib/ruby_ui/docs/base.rb | 56 +++++++++++++++++++++++++ lib/ruby_ui/docs/sidebar_examples.rb | 22 ++++++++++ lib/ruby_ui/docs/visual_code_example.rb | 5 ++- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 lib/ruby_ui/docs/sidebar_examples.rb diff --git a/lib/ruby_ui/docs/base.rb b/lib/ruby_ui/docs/base.rb index b3acbff9..9a63c97b 100644 --- a/lib/ruby_ui/docs/base.rb +++ b/lib/ruby_ui/docs/base.rb @@ -10,5 +10,61 @@ def Heading(level:, &) def component_files(component_name) [] end + + # Text helper for wrapping paragraphs + def Text(&) + p(&) + end + + # InlineLink helper for documentation links + def InlineLink(href:, target: nil, class: nil, &) + a(href: href, target: target, class: binding.local_variable_get(:class), &) + end + + # Alert component helpers + def Alert(&) + div(&) + end + + def AlertTitle(&) + h4(&) + end + + def AlertDescription(&) + p(&) + end + + # Route helper stubs - return "#" as placeholder + def docs_sheet_path + "#" + end + + def docs_separator_path + "#" + end + + def docs_accordion_path + "#" + end + + def docs_alert_path + "#" + end + + def docs_alert_dialog_path + "#" + end + + def docs_aspect_ratio_path + "#" + end + + def docs_avatar_path + "#" + end + + def docs_badge_path + "#" + end end end diff --git a/lib/ruby_ui/docs/sidebar_examples.rb b/lib/ruby_ui/docs/sidebar_examples.rb new file mode 100644 index 00000000..e9db4a34 --- /dev/null +++ b/lib/ruby_ui/docs/sidebar_examples.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Stub constants for sidebar documentation examples +# These are replaced with actual implementations in the web app + +module Views + module Docs + class Sidebar < Views::Base + class Example + CODE = <<~RUBY + # Sidebar example code placeholder + RUBY + end + + class InsetExample + CODE = <<~RUBY + # Sidebar inset example code placeholder + RUBY + end + end + end +end diff --git a/lib/ruby_ui/docs/visual_code_example.rb b/lib/ruby_ui/docs/visual_code_example.rb index 0007c3f3..39c6154d 100644 --- a/lib/ruby_ui/docs/visual_code_example.rb +++ b/lib/ruby_ui/docs/visual_code_example.rb @@ -2,15 +2,18 @@ module Docs class VisualCodeExample < Phlex::HTML - def initialize(title:, context:) + def initialize(title:, context:, description: nil, src: nil) @title = title @context = context + @description = description + @src = src end def view_template(&block) code = block.call div do h3 { @title } + p { @description } if @description pre { code } @context.instance_eval(code) end From 60635743895bc784d7f445f81535906dee311016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djalma=20Ara=C3=BAjo?= Date: Thu, 11 Dec 2025 14:35:18 -0300 Subject: [PATCH 7/7] Add documentation for batch 5 components (#340) Add documentation files for skeleton, switch, table, tabs, textarea, theme_toggle, tooltip, and typography components. Also adds missing stub methods for typography docs (Components.Heading, Components.TypographyList, InlineCode, docs_installation_path). --- lib/ruby_ui/docs/base.rb | 20 ++ lib/ruby_ui/skeleton/skeleton_docs.rb | 29 +++ lib/ruby_ui/switch/switch_docs.rb | 46 ++++ lib/ruby_ui/table/table_docs.rb | 102 +++++++++ lib/ruby_ui/tabs/tabs_docs.rb | 211 ++++++++++++++++++ lib/ruby_ui/textarea/textarea_docs.rb | 54 +++++ lib/ruby_ui/theme_toggle/theme_toggle_docs.rb | 71 ++++++ lib/ruby_ui/tooltip/tooltip_docs.rb | 52 +++++ lib/ruby_ui/typography/typography_docs.rb | 107 +++++++++ 9 files changed, 692 insertions(+) create mode 100644 lib/ruby_ui/skeleton/skeleton_docs.rb create mode 100644 lib/ruby_ui/switch/switch_docs.rb create mode 100644 lib/ruby_ui/table/table_docs.rb create mode 100644 lib/ruby_ui/tabs/tabs_docs.rb create mode 100644 lib/ruby_ui/textarea/textarea_docs.rb create mode 100644 lib/ruby_ui/theme_toggle/theme_toggle_docs.rb create mode 100644 lib/ruby_ui/tooltip/tooltip_docs.rb create mode 100644 lib/ruby_ui/typography/typography_docs.rb diff --git a/lib/ruby_ui/docs/base.rb b/lib/ruby_ui/docs/base.rb index 9a63c97b..459cf6a3 100644 --- a/lib/ruby_ui/docs/base.rb +++ b/lib/ruby_ui/docs/base.rb @@ -66,5 +66,25 @@ def docs_avatar_path def docs_badge_path "#" end + + def docs_installation_path + "#" + end + + # InlineCode helper for typography examples + def InlineCode(&) + code(&) + end + end +end + +# Module-level components stub +module Components + def self.Heading(level:, &block) + # Stub for module-level Heading calls + end + + def self.TypographyList(items:, numbered: false) + # Stub for TypographyList component end end diff --git a/lib/ruby_ui/skeleton/skeleton_docs.rb b/lib/ruby_ui/skeleton/skeleton_docs.rb new file mode 100644 index 00000000..44d311f9 --- /dev/null +++ b/lib/ruby_ui/skeleton/skeleton_docs.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Views::Docs::Skeleton < Views::Base + def view_template + component = "Skeleton" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Skeleton", description: "Use to show a placeholder while content is loading.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + div(class: "flex items-center space-x-4") do + Skeleton(class: "h-12 w-12 rounded-full") + div(class: "space-y-2") do + Skeleton(class: "h-4 w-[250px]") + Skeleton(class: "h-4 w-[200px]") + 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/switch/switch_docs.rb b/lib/ruby_ui/switch/switch_docs.rb new file mode 100644 index 00000000..8f4dcdc7 --- /dev/null +++ b/lib/ruby_ui/switch/switch_docs.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Views::Docs::Switch < Views::Base + def view_template + component = "Switch" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Switch", description: "A control that allows the user to toggle between checked and not checked.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Default", context: self) do + <<~RUBY + Switch(name: "switch") + RUBY + end + + render Docs::VisualCodeExample.new(title: "Checked", context: self) do + <<~RUBY + Switch(name: "switch", checked: true) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + Switch(name: "switch", disabled: true) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + Switch(name: "switch", aria: {disabled: "true"}) + RUBY + end + + render Docs::VisualCodeExample.new(title: "With flag include_hidden false", context: self) do + <<~RUBY + # Supports the creation of a hidden input to be used in forms inspired by the Ruby on Rails implementation of check_box. Default is true. + Switch(name: "switch", include_hidden: false) + RUBY + end + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/lib/ruby_ui/table/table_docs.rb b/lib/ruby_ui/table/table_docs.rb new file mode 100644 index 00000000..8ac4d8c0 --- /dev/null +++ b/lib/ruby_ui/table/table_docs.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +class Views::Docs::Table < Views::Base + Invoice = Struct.new(:identifier, :status, :method, :amount, keyword_init: true) + User = Struct.new(:avatar_url, :name, :username, :commits, :github_url, keyword_init: true) + + def view_template + component = "Table" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-8") do + render Docs::Header.new(title: "Table", description: "A responsive table component.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Without builder", context: self) do + <<~RUBY + Table do + TableCaption { "Employees at Acme inc." } + TableHeader do + TableRow do + TableHead { "Name" } + TableHead { "Email" } + TableHead { "Status" } + TableHead(class: "text-right") { "Role" } + end + end + TableBody do + invoices.each do |invoice| + TableRow do + TableCell(class: 'font-medium') { invoice.identifier } + TableCell { render_status_badge(invoice.status) } + TableCell { invoice.method } + TableCell(class: "text-right") { format_amount(invoice.amount) } + end + end + end + TableFooter do + TableRow do + TableHead(class: "font-medium", colspan: 3) { "Total" } + TableHead(class: "font-medium text-right") { format_amount(invoices.sum(&:amount)) } + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def invoices + [ + Invoice.new(identifier: "INV-0001", status: "Active", method: "Credit Card", amount: 100), + Invoice.new(identifier: "INV-0002", status: "Active", method: "Bank Transfer", amount: 230), + Invoice.new(identifier: "INV-0003", status: "Pending", method: "PayPal", amount: 350), + Invoice.new(identifier: "INV-0004", status: "Inactive", method: "Credit Card", amount: 100) + ] + end + + def users + [ + User.new(avatar_url: "https://avatars.githubusercontent.com/u/246692?v=4", name: "Joel Drapper", username: "joeldrapper", commits: 404), + User.new(avatar_url: "https://avatars.githubusercontent.com/u/33979976?v=4", name: "Alexandre Ruban", username: "alexandreruban", commits: 16), + User.new(avatar_url: "https://avatars.githubusercontent.com/u/77887?v=4", name: "Will Cosgrove", username: "willcosgrove", commits: 12), + User.new(avatar_url: "https://avatars.githubusercontent.com/u/3025661?v=4", name: "Stephann V.", username: "stephannv", commits: 8), + User.new(avatar_url: "https://avatars.githubusercontent.com/u/6411752?v=4", name: "Marco Roth", username: "marcoroth", commits: 8) + ] + end + + def github_link(user) + "https://github.com/#{user.username}" + end + + def github_icon + svg(viewbox: "0 0 438.549 438.549", class: "h-4 w-4") do |s| + s.path( + fill: "currentColor", + d: + "M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z" + ) + end + end + + def format_amount(amount) + "$#{amount}.00" + end + + def render_status_badge(status) + case status.downcase + when "active" + Badge(variant: :success, size: :sm) { status } + when "inactive" + Badge(variant: :destructive, size: :sm) { status } + when "pending" + Badge(variant: :warning, size: :sm) { status } + end + end +end diff --git a/lib/ruby_ui/tabs/tabs_docs.rb b/lib/ruby_ui/tabs/tabs_docs.rb new file mode 100644 index 00000000..33b6ff34 --- /dev/null +++ b/lib/ruby_ui/tabs/tabs_docs.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +class Views::Docs::Tabs < Views::Base + Repo = Struct.new(:github_url, :name, :stars, :version, keyword_init: true) + + def view_template + component = "Tabs" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Tabs", description: "A set of layered sections of content—known as tab panels—that are displayed one at a time.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Tabs(default_value: "account", class: 'w-96') do + TabsList do + TabsTrigger(value: "account") { "Account" } + TabsTrigger(value: "password") { "Password" } + end + TabsContent(value: "account") do + div(class: "rounded-lg border p-6 space-y-4 bg-background text-foreground") do + div(class: "space-y-0") do + Text(size: "4", weight: "semibold") { "Account" } + Text(size: "2", class: "text-muted-foreground") { "Update your account details." } + end + end + end + TabsContent(value: "password") do + div(class: "rounded-lg border p-6 space-y-4 bg-background text-foreground") do + div do + Text(size: "4", weight: "semibold") { "Password" } + Text(size: "2", class: "text-muted-foreground") { "Change your password here. After saving, you'll be logged out." } + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + Tabs(default_value: "account", class: 'w-96') do + TabsList do + TabsTrigger(disabled: true, value: "account") { "Account" } + TabsTrigger(disabled: true, value: "password") { "Password" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + Tabs(default_value: "account", class: 'w-96') do + TabsList do + TabsTrigger(aria: {disabled: "true"}, value: "account") { "Account" } + TabsTrigger(aria: {disabled: "true"}, value: "password") { "Password" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Full width", context: self) do + <<~RUBY + Tabs(default_value: "overview", class: 'w-96') do + TabsList(class: 'w-full grid grid-cols-2') do + TabsTrigger(value: "overview") do + book_icon + span(class: 'ml-2') { "Overview" } + end + TabsTrigger(value: "repositories") do + repo_icon + span(class: 'ml-2') { "Repositories" } + end + end + TabsContent(value: "overview") do + div(class: "rounded-lg border p-6 bg-background text-foreground flex justify-between space-x-4") do + Avatar do + AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper") + AvatarFallback { "JD" } + end + div(class: "space-y-4") do + div do + Text(size: "4", weight: "semibold") { "Joel Drapper" } + Text(size: "2", class: "text-muted-foreground") { "Creator of Phlex Components. Ruby on Rails developer." } + end + Link(href: "https://github.com/joeldrapper", variant: :outline, size: :sm) do + github_icon + span(class: 'ml-2') { "View profile" } + end + end + end + end + TabsContent(value: "repositories") do + div(class: "rounded-lg border p-6 space-y-4 bg-background text-foreground") do + repo = repositories.first + Link(href: repo.github_url, variant: :link, class: "pl-0") { repo.name } + Badge { repo.version } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Change default value", context: self) do + <<~RUBY + Tabs(default: "password", class: 'w-96') do + TabsList do + TabsTrigger(value: "account") { "Account" } + TabsTrigger(value: "password") { "Password" } + end + TabsContent(value: "account") do + div(class: "rounded-lg border p-6 space-y-4 bg-background text-foreground") do + div(class: "space-y-0") do + Text(size: "4", weight: "semibold") { "Account" } + Text(size: "2", class: "text-muted-foreground") { "Update your account details." } + end + end + end + TabsContent(value: "password") do + div(class: "rounded-lg border p-6 space-y-4 bg-background text-foreground") do + div do + Text(size: "4", weight: "semibold") { "Password" } + Text(size: "2", class: "text-muted-foreground") { "Change your password here. After saving, you'll be logged out." } + end + end + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def book_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewbox: "0 0 24 24", + stroke_width: "2", + stroke: "currentColor", + class: "w-4 h-4 text-muted-foreground" + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" + ) + end + end + + def repo_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewbox: "0 0 24 24", + stroke_width: "2", + stroke: "currentColor", + class: "w-4 h-4 text-muted-foreground" + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" + ) + end + end + + def github_icon + svg(viewbox: "0 0 438.549 438.549", class: "h-4 w-4") do |s| + s.path( + fill: "currentColor", + d: + "M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z" + ) + end + end + + def star_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewbox: "0 0 24 24", + stroke_width: "2", + stroke: "currentColor", + class: "w-4 h-4 text-amber-500" + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" + ) + end + end + + def repositories + [ + Repo.new(github_url: "https://github.com/phlex-ruby/phlex", name: "phlex", stars: 961, version: "v1.8.1"), + Repo.new(github_url: "https://github.com/joeldrapper/green_dots", name: "green_dots", stars: 40, version: "1.0"), + Repo.new(github_url: "https://github.com/joeldrapper/literal", name: "literal", stars: 96, version: "v0.1.0") + ] + end +end diff --git a/lib/ruby_ui/textarea/textarea_docs.rb b/lib/ruby_ui/textarea/textarea_docs.rb new file mode 100644 index 00000000..ce83349a --- /dev/null +++ b/lib/ruby_ui/textarea/textarea_docs.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Views::Docs::Textarea < Views::Base + def view_template + component = "Textarea" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Textarea", description: "Displays a textarea field.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Textarea", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + Textarea(placeholder: "Textarea") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + Textarea(disabled: true, placeholder: "Disabled") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + Textarea(aria: {disabled: "true"}, placeholder: "Aria Disabled") + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With FormField", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + FormField do + FormFieldLabel(for: "textarea") { "Textarea" } + FormFieldHint { "This is a textarea" } + Textarea(placeholder: "Textarea", id: "textarea") + FormFieldError() + end + end + RUBY + end + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end +end diff --git a/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb b/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb new file mode 100644 index 00000000..1740a924 --- /dev/null +++ b/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class Views::Docs::ThemeToggle < Views::Base + def view_template + component = "ThemeToggle" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Theme Toggle", description: "Toggle between dark/light theme.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "With icon", context: self) do + <<~RUBY + ThemeToggle do |toggle| + SetLightMode do + Button(variant: :ghost, icon: true) do + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-4 h-4" + ) do |s| + s.path( + d: + "M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z" + ) + end + end + end + + SetDarkMode do + Button(variant: :ghost, icon: true) do + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "currentColor", + class: "w-4 h-4" + ) do |s| + s.path( + fill_rule: "evenodd", + d: + "M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z", + clip_rule: "evenodd" + ) + end + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "With text", context: self) do + <<~RUBY + ThemeToggle do |toggle| + SetLightMode do + Button(variant: :primary) { "Light" } + end + + SetDarkMode do + Button(variant: :primary) { "Dark" } + 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/tooltip/tooltip_docs.rb b/lib/ruby_ui/tooltip/tooltip_docs.rb new file mode 100644 index 00000000..5189728b --- /dev/null +++ b/lib/ruby_ui/tooltip/tooltip_docs.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class Views::Docs::Tooltip < Views::Base + def view_template + component = "Tooltip" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Tooltip", description: "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Example", context: self) do + <<~RUBY + Tooltip do + TooltipTrigger do + Button(variant: :outline, icon: true) do + bookmark_icon + end + end + TooltipContent do + Text { "Add to library" } + end + end + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end + + private + + def bookmark_icon + 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" + ) do |s| + s.path( + stroke_linecap: "round", + stroke_linejoin: "round", + d: + "M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" + ) + end + end +end diff --git a/lib/ruby_ui/typography/typography_docs.rb b/lib/ruby_ui/typography/typography_docs.rb new file mode 100644 index 00000000..cf860dcf --- /dev/null +++ b/lib/ruby_ui/typography/typography_docs.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +class Views::Docs::Typography < Views::Base + def view_template + component = "Typography" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Typography", description: "Sensible defaults to use for text.") + + Components.Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "h1", context: self) do + <<~RUBY + Heading(level: 1) { "This is an H1 title" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "h2", context: self) do + <<~RUBY + Heading(level: 2) { "This is an H2 title" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "h3", context: self) do + <<~RUBY + Heading(level: 3) { "This is an H3 title" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "h4", context: self) do + <<~RUBY + Heading(level: 4) { "This is an H4 title" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "p", context: self) do + <<~RUBY + Text { "This is an P tag" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Inline Link", context: self) do + <<~RUBY + Text(class: 'text-center') do + plain "Checkout our " + InlineLink(href: docs_installation_path) { "installation instructions" } + plain " to get started." + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "List", context: self) do + <<~RUBY + Components.TypographyList(items: [ + 'Phlex is fast', + 'Phlex is easy to use', + 'Phlex is awesome', + ]) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Numbered List", context: self) do + <<~RUBY + Components.TypographyList(items: [ + 'Copy', + 'Paste', + 'Customize', + ], numbered: true) + RUBY + end + + render Docs::VisualCodeExample.new(title: "Inline Code", context: self) do + <<~RUBY + InlineCode { "This is an inline code block" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Lead", context: self) do + <<~RUBY + Text(as: "p", size: "5", weight: "muted") { "A modal dialog that interrupts the user with important content and expects a response." } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Large", context: self) do + <<~RUBY + Text(size: "4", weight: "semibold") { "Are you sure absolutely sure?" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Small", context: self) do + <<~RUBY + Text(size: "sm") { "Email address" } + RUBY + end + + render Docs::VisualCodeExample.new(title: "Muted", context: self) do + <<~RUBY + Text(size: "2", class: "text-muted-foreground") { "Enter your email address." } + RUBY + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end