diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..a6fce0a1c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +@CLAUDE.md \ No newline at end of file diff --git a/Gemfile b/Gemfile index a99f1fd6e..a5b4d2c37 100644 --- a/Gemfile +++ b/Gemfile @@ -79,5 +79,6 @@ gem "ruby_ui", github: "ruby-ui/ruby_ui", branch: "main", require: false gem "pry", "0.16.0" gem "tailwind_merge", "~> 1.4.0" +gem "rss", "0.3.1" gem "rouge", "~> 4.7" diff --git a/Gemfile.lock b/Gemfile.lock index f602cd579..7b32c8a98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -250,6 +250,8 @@ GEM io-console (~> 0.5) rexml (3.4.4) rouge (4.7.0) + rss (0.3.1) + rexml rubocop (1.84.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -342,6 +344,7 @@ DEPENDENCIES puma (= 7.2.0) rails (= 8.1.3) rouge (~> 4.7) + rss (= 0.3.1) ruby_ui! selenium-webdriver sqlite3 (= 2.9.2) diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 000000000..6a63f0073 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index ed1f1b64d..67fc2752a 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -135,6 +135,10 @@ --color-warning-foreground: var(--warning-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); + + /* Fonts */ + --font-sans: "Inter", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"; + --font-heading: "Inter", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"; } /* Container settings */ @@ -149,6 +153,9 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground font-sans antialiased; + } + h1, h2, h3, h4, h5, h6 { + @apply tracking-tight; } } diff --git a/app/components/base.rb b/app/components/base.rb index 88d3b61e8..c8d208916 100644 --- a/app/components/base.rb +++ b/app/components/base.rb @@ -8,6 +8,8 @@ class Components::Base < Phlex::HTML include Phlex::Rails::Helpers::ImageURL include Phlex::Rails::Helpers::Flash include Phlex::Rails::Helpers::Request + include Phlex::Rails::Helpers::ContentTag + include LucideRails::RailsHelper TAILWIND_MERGER = ::TailwindMerge::Merger.new.freeze unless defined?(TAILWIND_MERGER) diff --git a/app/components/docs/header.rb b/app/components/docs/header.rb index fb3940831..45433952b 100644 --- a/app/components/docs/header.rb +++ b/app/components/docs/header.rb @@ -10,8 +10,8 @@ def initialize(title: nil, description: nil) def view_template div(class: "space-y-2") do - Components.Heading(level: 1) { @title } - Text(as: "p", size: "5", class: "text-muted-foreground") { @description } + h1(class: "scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl") { @title } + p(class: "text-lg text-foreground") { @description } end end diff --git a/app/components/docs/tailwind_config.rb b/app/components/docs/tailwind_config.rb index 146739bd0..40d9cd669 100644 --- a/app/components/docs/tailwind_config.rb +++ b/app/components/docs/tailwind_config.rb @@ -21,7 +21,7 @@ def tailwind_config const execSync = require('child_process').execSync; // Import ruby_ui gem path (To make sure Tailwind loads classes used by ruby_ui gem) - const outputRUBYUI = execSync('bundle show phlex_ui', { encoding: 'utf-8' }); + const outputRUBYUI = execSync('bundle show ruby_ui', { encoding: 'utf-8' }); const ruby_ui_path = outputRUBYUI.trim() + '/**/*.rb'; const defaultTheme = require('tailwindcss/defaultTheme') diff --git a/app/components/home_view/banner.rb b/app/components/home_view/banner.rb deleted file mode 100644 index 0ce60f551..000000000 --- a/app/components/home_view/banner.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Components - module HomeView - class Banner < Components::Base - include DeferredRender - - def view_template(&block) - div(class: "relative overflow-hidden") do - render Shared::GridPattern.new(spacing: :md) - render HomeView::Shapes.new(size: :xl, color: :pink, class: "hidden sm:block absolute right-0 top-1/2 transform -translate-y-1/2 sm:translate-x-2/3 md:translate-x-1/2") - div(class: "relative container mx-auto max-w-3xl py-24 px-4 sm:text-center flex flex-col sm:items-center gap-y-6") do - Heading(level: 1) do - plain "A UI component library, crafted precisely for Ruby devs" - span(class: "text-primary/70") { " who want to stay organised and build modern apps, fast." } - end - if @cta - div(class: "grid grid-cols-1 sm:grid-cols-2 gap-4", &@cta) - end - end - end - end - - def cta(&block) - @cta = block - end - end - end -end diff --git a/app/components/home_view/card.rb b/app/components/home_view/card.rb deleted file mode 100644 index f6ddbaba8..000000000 --- a/app/components/home_view/card.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module Components - module HomeView - class Card < Components::Base - include DeferredRender - - def initialize(attributes = {}) - @attributes = attributes - @title = attributes.delete(:title) - @subtitle = attributes.delete(:subtitle) || nil - @color = attributes.delete(:color) || :card - - @color_classes = { - card: "bg-background border", - primary: "bg-primary text-white", - secondary: "bg-accent text-accent-foreground", - # Add sky, violet, amber, lime, pink - sky: "bg-sky-100 text-sky-950", - violet: "bg-violet-100 text-violet-950", - amber: "bg-amber-100 text-amber-950", - lime: "bg-lime-100 text-lime-950", - pink: "bg-pink-100 text-pink-950" - } - end - - def view_template(&block) - div(**@attributes, class: ["relative flex flex-col p-6 md:p-10 rounded-2xl space-y-8 overflow-hidden", @attributes[:class], @color_classes[@color.to_sym]]) do - if @icon - div(&@icon) - end - if @title || @subtitle - div(class: "space-y-2") do - h2(class: "font-semibold leading-none tracking-tight") { @title } if @title - p { @subtitle } if @subtitle - end - end - - @content&.call - end - end - - def icon(&block) - @icon = block - end - - def content(&block) - @content = block - end - end - end -end diff --git a/app/components/home_view/shapes.rb b/app/components/home_view/shapes.rb deleted file mode 100644 index 757fb1bd9..000000000 --- a/app/components/home_view/shapes.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -module Components - module HomeView - class Shapes < Components::Base - def initialize(size: :md, **attributes) - @attributes = attributes - @circle_sizes = { - xs: "w-8 h-8", - sm: "w-12 h-12", - md: "w-16 h-16", - lg: "w-24 h-24", - xl: "w-32 h-32" - } - - @square_sizes = { - xs: "w-16 h-16 rounde-md", - sm: "w-24 h-24 rounded-md", - md: "w-32 h-32 rounded-lg", - lg: "w-48 h-48 rounded-lg", - xl: "w-64 h-64 rounded-xl" - } - - @size = size - @colors = %w[sky violet pink amber lime] - @color = @attributes.delete(:color) || @colors.sample - end - - def view_template - # Create one square and one circle of different colors - # The square will be on a rotation of 15deg - # the circle will be a solid color - # The shapes will be slightly overlapping - div(**@attributes) do - div(class: "relative") do - circle - square - end - end - end - - def square - color_classes = { - sky: "bg-sky-500/10 backdrop-blur-3xl ring-1 ring-sky-600/20 saturate-150", - violet: "bg-violet-500/10 backdrop-blur-3xl ring-1 ring-violet-600/20 saturate-150", - pink: "bg-pink-500/10 backdrop-blur-3xl ring-1 ring-pink-600/20 saturate-150", - amber: "bg-amber-500/10 backdrop-blur-3xl ring-1 ring-amber-600/20 saturate-150", - lime: "bg-lime-500/10 backdrop-blur-3xl ring-1 ring-lime-600/20 saturate-150" - } - - sample_color = color_classes[@color.to_sym] - - div(class: "#{@square_sizes[@size]} #{sample_color} -rotate-12") - end - - def circle - color_classes = { - sky: "bg-sky-400", - violet: "bg-violet-400", - pink: "bg-pink-400", - amber: "bg-amber-400", - lime: "bg-lime-400" - } - - sample_color = color_classes[@color.to_sym] - - div(class: "#{@circle_sizes[@size]} #{sample_color} rounded-full absolute top-0 left-0 transform -translate-x-1/2 -translate-y-1/2") - end - end - end -end diff --git a/app/components/home_view/steps.rb b/app/components/home_view/steps.rb deleted file mode 100644 index 28383c6bb..000000000 --- a/app/components/home_view/steps.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Components - module HomeView - class Steps < Components::Base - def initialize(attributes = {}) - @attributes = attributes - @steps = attributes.delete(:steps) || [] - end - - def view_template(&block) - div(**@attributes) do - @steps.each_with_index do |step, index| - render_step(step, index: index, last: index == @steps.length - 1) - end - end - end - - def render_step(step, index:, last: false) - div(class: "relative flex space-x-8") do - div(class: "flex-shrink-0 h-full") do - div(class: "flex-0 flex items-center justify-center h-6 w-6 rounded-full border bg-background") do - p(class: "font-medium") { index + 1 } - end - # vertical line unless last - hr(class: "absolute left-3 top-6 -ml-px h-full w-px bg-border") unless last - end - div(class: "flex-grow space-y-2 pb-10 pt-1") do - h3(class: "font-semibold leading-none tracking-tight") { step[:title] } if step[:title] - p { step[:description] } if step[:description] - end - end - end - end - end -end diff --git a/app/components/shared/components_list.rb b/app/components/shared/components_list.rb new file mode 100644 index 000000000..49c824905 --- /dev/null +++ b/app/components/shared/components_list.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Components + module Shared + module ComponentsList + def components + [ + {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}, + {name: "Breadcrumb", path: docs_breadcrumb_path}, + {name: "Button", path: docs_button_path}, + {name: "Calendar", path: docs_calendar_path}, + {name: "Card", path: docs_card_path}, + {name: "Carousel", path: docs_carousel_path}, + # { name: "Chart", path: docs_chart_path }, + {name: "Checkbox", path: docs_checkbox_path}, + {name: "Checkbox Group", path: docs_checkbox_group_path}, + {name: "Clipboard", path: docs_clipboard_path}, + {name: "Codeblock", path: docs_codeblock_path}, + {name: "Collapsible", path: docs_collapsible_path}, + {name: "Combobox", path: docs_combobox_path}, + {name: "Command", path: docs_command_path}, + {name: "Context Menu", path: docs_context_menu_path}, + {name: "Date Picker", path: docs_date_picker_path}, + {name: "Dialog / Modal", path: docs_dialog_path}, + {name: "Dropdown Menu", path: docs_dropdown_menu_path}, + {name: "Form", path: docs_form_path}, + {name: "Hover Card", path: docs_hover_card_path}, + {name: "Input", path: docs_input_path}, + {name: "Link", path: docs_link_path}, + {name: "Masked Input", path: masked_input_path}, + {name: "Pagination", path: docs_pagination_path}, + {name: "Popover", path: docs_popover_path}, + {name: "Progress", path: docs_progress_path}, + {name: "Radio Button", path: docs_radio_button_path}, + {name: "Native Select", path: docs_native_select_path}, + {name: "Select", path: docs_select_path}, + {name: "Separator", path: docs_separator_path}, + {name: "Sheet", path: docs_sheet_path}, + {name: "Shortcut Key", path: docs_shortcut_key_path}, + {name: "Sidebar", path: docs_sidebar_path}, + {name: "Skeleton", path: docs_skeleton_path}, + {name: "Switch", path: docs_switch_path}, + {name: "Table", path: docs_table_path}, + {name: "Tabs", path: docs_tabs_path}, + {name: "Textarea", path: docs_textarea_path}, + {name: "Theme Toggle", path: docs_theme_toggle_path}, + {name: "Tooltip", path: docs_tooltip_path}, + {name: "Typography", path: docs_typography_path} + ] + end + end + end +end diff --git a/app/components/shared/footer.rb b/app/components/shared/footer.rb new file mode 100644 index 000000000..13dbcc87e --- /dev/null +++ b/app/components/shared/footer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Components + module Shared + class Footer < Components::Base + def view_template + footer(class: "py-6 bg-background") do + div(class: "container flex flex-col items-center justify-center gap-4 md:h-12 md:flex-row") do + p(class: "text-balance text-center text-sm leading-loose text-foreground") do + plain "Heavily inspired by " + a( + href: "https://ui.shadcn.com", + target: "_blank", + rel: "noreferrer", + class: "font-medium underline underline-offset-4" + ) { "shadcn" } + plain ". The source code is available on " + a( + href: "https://github.com/ruby-ui/ruby_ui", + target: "_blank", + rel: "noreferrer", + class: "font-medium underline underline-offset-4" + ) { "GitHub" } + plain "." + end + end + end + end + end + end +end diff --git a/app/components/shared/logo.rb b/app/components/shared/logo.rb index a12f7ff24..4d06f7b04 100644 --- a/app/components/shared/logo.rb +++ b/app/components/shared/logo.rb @@ -9,7 +9,7 @@ def view_template img(src: image_url("logo.svg"), class: "h-4 block dark:hidden") img(src: image_url("logo_dark.svg"), class: "h-4 hidden dark:block") span(class: "sr-only") { "RubyUI" } - Badge(variant: :amber, size: :sm, class: "ml-2 whitespace-nowrap") { "1.0" } + Badge(class: "ml-2 whitespace-nowrap bg-black text-white hover:bg-black/90 px-1.5 py-0.5 rounded-full text-xs font-semibold") { "1.0" } } end end diff --git a/app/components/shared/menu.rb b/app/components/shared/menu.rb index 3cb1150b1..8046f4a2f 100644 --- a/app/components/shared/menu.rb +++ b/app/components/shared/menu.rb @@ -3,16 +3,18 @@ module Components module Shared class Menu < Components::Base + include ComponentsList + def view_template div(class: "pb-4") do # Main routes (Docs, Components, Themes, Github, Discord, Twitter) div(class: "md:hidden") do main_link("Docs", docs_introduction_path) - main_link("Components", docs_accordion_path) + main_link("Components", docs_components_path) main_link("Themes", theme_path("default")) - main_link("Github", "https://github.com/PhlexUI/phlex_ui") + main_link("Github", "https://github.com/ruby-ui/ruby_ui") + main_link("Discord", ENV["DISCORD_INVITE_LINK"]) main_link("Discord", ENV["DISCORD_INVITE_LINK"]) - main_link("Twitter", ENV["PHLEXUI_TWITTER_LINK"]) end # GETTING STARTED @@ -34,7 +36,6 @@ def view_template # COMPONENTS h4(class: "mb-1 mt-4 rounded-md px-2 py-1 text-sm font-semibold flex items-center gap-x-2") do plain "Components" - Badge(variant: :primary, size: :sm) { components.count.to_s } end div(class: "grid grid-flow-row auto-rows-max text-sm") do components.each do |component| @@ -50,7 +51,8 @@ def getting_started_links {name: "Installation", path: docs_installation_path}, {name: "Dark mode", path: docs_dark_mode_path}, {name: "Theming", path: docs_theming_path}, - {name: "Customizing components", path: docs_customizing_components_path} + {name: "Customizing components", path: docs_customizing_components_path}, + {name: "Changelog", path: docs_changelog_path} ] end @@ -61,69 +63,16 @@ def installation_links ] end - def components - [ - {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}, - {name: "Breadcrumb", path: docs_breadcrumb_path, badge: "New"}, - {name: "Button", path: docs_button_path}, - {name: "Calendar", path: docs_calendar_path}, - {name: "Card", path: docs_card_path}, - {name: "Carousel", path: docs_carousel_path, badge: "New"}, - # { name: "Chart", path: docs_chart_path, badge: "New" }, - {name: "Checkbox", path: docs_checkbox_path}, - {name: "Checkbox Group", path: docs_checkbox_group_path}, - {name: "Clipboard", path: docs_clipboard_path}, - {name: "Codeblock", path: docs_codeblock_path}, - {name: "Collapsible", path: docs_collapsible_path}, - {name: "Combobox", path: docs_combobox_path, badge: "Updated"}, - {name: "Command", path: docs_command_path}, - {name: "Context Menu", path: docs_context_menu_path}, - {name: "Date Picker", path: docs_date_picker_path}, - {name: "Dialog / Modal", path: docs_dialog_path}, - {name: "Dropdown Menu", path: docs_dropdown_menu_path}, - {name: "Form", path: docs_form_path}, - {name: "Hover Card", path: docs_hover_card_path}, - {name: "Input", path: docs_input_path}, - {name: "Link", path: docs_link_path}, - {name: "Masked Input", path: masked_input_path}, - {name: "Pagination", path: docs_pagination_path, badge: "New"}, - {name: "Popover", path: docs_popover_path}, - {name: "Progress", path: docs_progress_path, badge: "New"}, - {name: "Radio Button", path: docs_radio_button_path, badge: "New"}, - {name: "Select", path: docs_select_path, badge: "New"}, - {name: "Separator", path: docs_separator_path, badge: "New"}, - {name: "Sheet", path: docs_sheet_path}, - {name: "Shortcut Key", path: docs_shortcut_key_path}, - {name: "Sidebar", path: docs_sidebar_path, badge: "New"}, - {name: "Skeleton", path: docs_skeleton_path, badge: "New"}, - {name: "Switch", path: docs_switch_path}, - {name: "Table", path: docs_table_path}, - {name: "Tabs", path: docs_tabs_path}, - {name: "Textarea", path: docs_textarea_path}, - {name: "Theme Toggle", path: docs_theme_toggle_path}, - {name: "Tooltip", path: docs_tooltip_path}, - {name: "Typography", path: docs_typography_path} - ] - end - def menu_link(component) current_path = component[:path] == request.path a( href: component[:path], class: [ - "group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline", - (current_path ? "text-foreground font-medium" : "text-muted-foreground") + "group flex w-full items-center rounded-md border border-transparent px-2 py-0.5 transition-colors", + (current_path ? "text-foreground font-semibold" : "text-foreground hover:bg-zinc-100 dark:hover:bg-zinc-800") ] ) do - span(class: "flex items-center gap-x-1") do - span { component[:name] } - Badge(variant: :success, size: :sm, class: "ml-1") { component[:badge] } if component[:badge] - end + component[:name] end end @@ -133,7 +82,7 @@ def main_link(name, path) href: path, class: [ "group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline", - (current_path ? "text-foreground font-medium" : "text-muted-foreground") + (current_path ? "text-foreground font-medium" : "text-foreground/80") ] ) do name diff --git a/app/components/shared/navbar.rb b/app/components/shared/navbar.rb index aa8f34dab..c8d45178e 100644 --- a/app/components/shared/navbar.rb +++ b/app/components/shared/navbar.rb @@ -3,22 +3,29 @@ module Components module Shared class Navbar < Components::Base + include ComponentsList + def view_template header(class: "supports-backdrop-blur:bg-background/80 sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-2xl backdrop-saturate-200") do div(class: "px-2 sm:px-4 sm:container flex h-14 items-center justify-between") do div(class: "mr-4 flex items-center") do render Shared::MobileMenu.new(class: "md:hidden") render Shared::Logo.new - - Link(href: docs_introduction_path, variant: :ghost, class: "hidden md:inline-block") { "Docs" } - Link(href: docs_accordion_path, variant: :ghost, class: "hidden md:inline-block") { "Components" } - Link(href: theme_path("default"), variant: :ghost, class: "hidden md:inline-block") { "Themes" } + nav(class: "hidden md:flex items-center gap-6 text-sm font-medium") do + a(href: docs_introduction_path, class: "transition-colors hover:text-foreground/80 text-foreground") { "Docs" } + a(href: docs_components_path, class: "transition-colors hover:text-foreground/80 text-foreground") { "Components" } + a(href: theme_path("default"), class: "transition-colors hover:text-foreground/80 text-foreground") { "Themes" } + end end div(class: "flex items-center gap-x-2 md:divide-x") do - div(class: "flex items-center") do - twitter_link - github_link - dark_mode_toggle + div(class: "flex items-center w-full justify-between md:justify-end gap-2") do + div(class: "w-full flex-1 md:w-auto md:flex-none") do + search_button + end + div(class: "flex items-center") do + github_link + dark_mode_toggle + end end end end @@ -62,24 +69,47 @@ def dark_mode_toggle end end - def twitter_link - Link(href: "https://twitter.com/phlexui", variant: :ghost, icon: true) do - svg( - viewbox: "0 0 20 20", - aria_hidden: "true", - fill: "currentColor", - class: "h-4 w-4" - ) do |s| - s.path( - d: - "M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0 0 20 3.92a8.19 8.19 0 0 1-2.357.646 4.118 4.118 0 0 0 1.804-2.27 8.224 8.224 0 0 1-2.605.996 4.107 4.107 0 0 0-6.993 3.743 11.65 11.65 0 0 1-8.457-4.287 4.106 4.106 0 0 0 1.27 5.477A4.073 4.073 0 0 1 .8 7.713v.052a4.105 4.105 0 0 0 3.292 4.022 4.095 4.095 0 0 1-1.853.07 4.108 4.108 0 0 0 3.834 2.85A8.233 8.233 0 0 1 0 16.407a11.615 11.615 0 0 0 6.29 1.84" - ) + def search_button + CommandDialog do + CommandDialogTrigger(keybindings: ["keydown.ctrl+k@window", "keydown.meta+k@window"]) do + Button(variant: :outline, class: "relative h-8 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64") do + svg(xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewbox: "0 0 24 24", fill: "none", stroke: "currentColor", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", class: "mr-2") { |s| + s.circle(cx: "11", cy: "11", r: "8") + s.path(d: "m21 21-4.3-4.3") + } + span(class: "hidden lg:inline-flex") { "Search documentation..." } + span(class: "inline-flex lg:hidden") { "Search..." } + kbd(class: "pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex") do + span(class: "text-xs") { "⌘" } + plain "K" + end + end + end + CommandDialogContent(class: "overflow-hidden p-0 shadow-2xl") do + Command(class: "flex h-full w-full flex-col overflow-hidden") do + CommandInput(placeholder: "Search documentation...", class: "border-none focus:ring-0") + CommandEmpty { "No results found." } + CommandList(class: "max-h-[min(450px,80vh)] overflow-y-auto p-2") do + CommandGroup(title: "Components") do + components.each do |component| + CommandItem(value: component[:name], href: component[:path], class: "px-2 py-1.5") do + plain component[:name] + end + end + end + CommandGroup(title: "Links") do + CommandItem(value: "Introduction", href: docs_introduction_path, class: "px-2 py-1.5") { "Introduction" } + CommandItem(value: "Installation", href: docs_installation_path, class: "px-2 py-1.5") { "Installation" } + CommandItem(value: "Theming", href: docs_theming_path, class: "px-2 py-1.5") { "Theming" } + end + end + end end end end def github_link - Link(href: "https://github.com/PhlexUI/phlex_ui", variant: :ghost, icon: true) do + Link(href: "https://github.com/ruby-ui/ruby_ui", variant: :ghost, icon: true) do github_icon end end diff --git a/app/components/shared/sidebar.rb b/app/components/shared/sidebar.rb index 9b072252c..49a5bebfc 100644 --- a/app/components/shared/sidebar.rb +++ b/app/components/shared/sidebar.rb @@ -5,8 +5,9 @@ module Shared class Sidebar < Components::Base def view_template aside(class: "fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block") do - div(class: "relative overflow-hidden h-full py-6 pl-8 pr-6 lg:py-8", style: "position:relative;--radix-scroll-area-corner-width:0px;--radix-scroll-area-corner-height:0px") do - div(class: "h-full w-full rounded-[inherit]", style: "overflow: hidden scroll;", data_controller: "sidebar-menu") do + div(class: "relative overflow-hidden h-full py-6 pl-8 pr-6 lg:py-8") do + # Updated Scroll wrapper using CSS to hide scrollbar + div(class: "h-full w-full rounded-[inherit] overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none] pb-10", data_controller: "sidebar-menu") do render Shared::Menu.new end end diff --git a/app/components/themes/grid/command.rb b/app/components/themes/grid/command.rb index 789b8bdf5..d0ad5e9e2 100644 --- a/app/components/themes/grid/command.rb +++ b/app/components/themes/grid/command.rb @@ -51,16 +51,16 @@ def view_template def components [ - ::Docs::ComponentStruct.new(name: "CommandController", source: "https://github.com/PhlexUI/phlex_ui_stimulus_pro/blob/main/controllers/command_controller.js", built_using: :stimulus), - ::Docs::ComponentStruct.new(name: "CommandDialog", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/dialog.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandDialogTrigger", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/dialog_trigger.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandDialogContent", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/dialog_content.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "Command", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandInput", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/input.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandEmpty", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/empty.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandList", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/list.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandGroup", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/group.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "CommandItem", source: "https://github.com/PhlexUI/phlex_ui_pro/blob/main/lib/phlex_ui_pro/command/item.rb", built_using: :phlex) + ::Docs::ComponentStruct.new(name: "CommandController", source: "https://github.com/ruby-ui/ruby_ui_stimulus_pro/blob/main/controllers/command_controller.js", built_using: :stimulus), + ::Docs::ComponentStruct.new(name: "CommandDialog", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/dialog.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandDialogTrigger", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/dialog_trigger.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandDialogContent", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/dialog_content.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "Command", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandInput", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/input.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandEmpty", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/empty.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandList", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/list.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandGroup", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/group.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "CommandItem", source: "https://github.com/ruby-ui/ruby_ui_pro/blob/main/lib/ruby_ui_pro/command/item.rb", built_using: :phlex) ] end diff --git a/app/components/themes/grid/repo_tabs.rb b/app/components/themes/grid/repo_tabs.rb index 2184e0e48..bd7ce8aa0 100644 --- a/app/components/themes/grid/repo_tabs.rb +++ b/app/components/themes/grid/repo_tabs.rb @@ -43,11 +43,11 @@ def view_template def components [ - ::Docs::ComponentStruct.new(name: "TabsController", source: "https://github.com/PhlexUI/phlex_ui_stimulus/blob/main/controllers/tabs_controller.js", built_using: :stimulus), - ::Docs::ComponentStruct.new(name: "Tabs", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/tabs.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "TabsList", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/tabs/list.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "TabsTrigger", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/tabs/trigger.rb", built_using: :phlex), - ::Docs::ComponentStruct.new(name: "TabsContent", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/tabs/content.rb", built_using: :phlex) + ::Docs::ComponentStruct.new(name: "TabsController", source: "https://github.com/ruby-ui/ruby_ui_stimulus/blob/main/controllers/tabs_controller.js", built_using: :stimulus), + ::Docs::ComponentStruct.new(name: "Tabs", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/tabs.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "TabsList", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/tabs/list.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "TabsTrigger", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/tabs/trigger.rb", built_using: :phlex), + ::Docs::ComponentStruct.new(name: "TabsContent", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/tabs/content.rb", built_using: :phlex) ] end diff --git a/app/components/themes/grid/table.rb b/app/components/themes/grid/table.rb index 81e894bba..0f8a61896 100644 --- a/app/components/themes/grid/table.rb +++ b/app/components/themes/grid/table.rb @@ -7,11 +7,11 @@ class Table < Components::Base User = Struct.new(:avatar_url, :name, :username, :commits, :github_url, keyword_init: true) # def view_template - # render PhlexUI::Card.new(class: "p-6") do - # render PhlexUI::Table::Builder.new(users) do |t| + # render RubyUI::Card.new(class: "p-6") do + # render RubyUI::Table::Builder.new(users) do |t| # t.column("Name") do |user| # div(class: "flex items-center space-x-3") do - # render PhlexUI::Avatar::Builder.new(src: user.avatar_url, size: :md) + # render RubyUI::Avatar::Builder.new(src: user.avatar_url, size: :md) # div do # p(class: "text-sm font-medium") { user.name } # p(class: "text-sm text-gray-500") { user.username } @@ -21,7 +21,7 @@ class Table < Components::Base # t.column("Commits", &:commits) # t.column("Links", header_attrs: {class: "text-right"}, footer_attrs: {class: "text-right"}) do |user| # div(class: "flex items-center justify-end space-x-2") do - # render PhlexUI::Link.new(href: github_link(user), variant: :outline, size: :sm) do + # render RubyUI::Link.new(href: github_link(user), variant: :outline, size: :sm) do # github_icon # span(class: "ml-2") { "See profile" } # end diff --git a/app/controllers/docs_controller.rb b/app/controllers/docs_controller.rb index 92382c692..071964b0b 100644 --- a/app/controllers/docs_controller.rb +++ b/app/controllers/docs_controller.rb @@ -34,6 +34,14 @@ def installation_rails_importmaps end # COMPONENTS + def components + render Views::Docs::Components.new + end + + def changelog + render Views::Docs::Changelog.new + end + def accordion render Views::Docs::Accordion.new end @@ -162,6 +170,10 @@ def radio_button render Views::Docs::RadioButton.new end + def native_select + render Views::Docs::NativeSelect.new + end + def select render Views::Docs::Select.new end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 14b9df619..71014d37b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -21,7 +21,7 @@ def component_files(component, gem_name = "ruby_ui") basename = File.basename(file, ext) name = basename.camelize - # source = "https://github.com/PhlexUI/phlex_ui/blob/v1/lib/ruby_ui/#{component.to_s.downcase}/#{File.basename(file)}" + # source = "https://github.com/ruby-ui/ruby_ui/blob/v1/lib/ruby_ui/#{component.to_s.downcase}/#{File.basename(file)}" source = "lib/ruby_ui/#{camel_to_snake(component)}/#{File.basename(file)}" built_using = if ext == ".rb" :phlex diff --git a/app/views/docs/accordion.rb b/app/views/docs/accordion.rb index 7f7558223..f899a3d76 100644 --- a/app/views/docs/accordion.rb +++ b/app/views/docs/accordion.rb @@ -15,13 +15,13 @@ def view_template Accordion do AccordionItem do AccordionTrigger do - p(class: "font-medium") { "What is PhlexUI?" } + p(class: "font-medium") { "What is RubyUI?" } 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." + "RubyUI is a UI component library for Ruby devs who want to build better, faster." end end end @@ -36,7 +36,7 @@ def view_template 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." + "Yes, RubyUI is pure Ruby and works great with Rails. It's a Ruby gem that you can install into your Rails app." end end end diff --git a/app/views/docs/changelog.rb b/app/views/docs/changelog.rb new file mode 100644 index 000000000..18066a731 --- /dev/null +++ b/app/views/docs/changelog.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "rss" +require "net/http" + +class Views::Docs::Changelog < Views::Base + def view_template + div(class: "mx-auto max-w-[800px] py-10") do + h1(class: "scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl mb-4") { "Changelog" } + p(class: "text-xl text-foreground mb-8") { "Latest updates and announcements from the RubyUI team." } + + if releases.any? + div(class: "space-y-12") do + releases.each do |release| + div(class: "flex flex-col items-start gap-4 md:flex-row md:items-baseline md:gap-8") do + div(class: "w-full md:w-32 shrink-0") do + p(class: "text-sm text-foreground/70") { format_date(release[:published_at]) } + end + div(class: "grid gap-4 w-full") do + h2(class: "text-2xl font-bold tracking-tight inline-flex items-center gap-2") do + plain release[:name] + end + div(class: "prose prose-slate dark:prose-invert max-w-none [&>ul]:my-6 [&>ul]:ml-6 [&>ul]:list-disc [&>ul>li]:mt-2 [&>h3]:text-xl [&>h3]:font-bold [&>h2]:text-2xl [&>h2]:font-bold") do + raw(release[:body].to_s.html_safe) + end + end + end + Separator(class: "my-8") + end + end + else + p(class: "text-muted-foreground") { "No releases found." } + end + end + end + + private + + def releases + @releases ||= begin + url = "https://github.com/ruby-ui/ruby_ui/releases.atom" + feed = RSS::Parser.parse(Net::HTTP.get(URI.parse(url)), false) + feed.items.map do |item| + { + name: item.title.content, + published_at: item.updated.content.to_s, + body: item.content.content + } + end + rescue + [] + end + end + + def format_date(date_string) + datetime = DateTime.parse(date_string) + datetime.strftime("%B %d, %Y") + rescue + date_string + end +end diff --git a/app/views/docs/chart.rb b/app/views/docs/chart.rb index 6aaf88ed0..70db2742d 100644 --- a/app/views/docs/chart.rb +++ b/app/views/docs/chart.rb @@ -108,8 +108,8 @@ def view_template 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) + ::Docs::ComponentStruct.new(name: "ChartController", source: "https://github.com/ruby-ui/ruby_ui_stimulus/blob/main/controllers/chart_controller.js", built_using: :stimulus), + ::Docs::ComponentStruct.new(name: "Chart", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/chart.rb", built_using: :phlex) ] end end diff --git a/app/views/docs/components.rb b/app/views/docs/components.rb new file mode 100644 index 000000000..9adea7bad --- /dev/null +++ b/app/views/docs/components.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Views::Docs::Components < Views::Base + include Components::Shared::ComponentsList + + def view_template + div(class: "mx-auto max-w-[800px] py-10") do + h1(class: "scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl mb-4") { "Components" } + p(class: "text-lg text-foreground mb-8 text-balance") { "A UI component library, crafted precisely for Ruby devs who want to stay organised and build modern apps, fast." } + + div(class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-4 mb-24 mt-12") do + components.each do |component| + a(href: component[:path], class: "text-base text-foreground/80 hover:text-foreground hover:underline transition-colors") do + component[:name] + end + end + end + + div(class: "rounded-xl border border-muted bg-muted/20 p-6") do + h3(class: "font-semibold text-lg tracking-tight mb-2") { "Missing a component?" } + p(class: "text-foreground mb-4") do + plain "Can't find the component you're looking for? Let us know what you'd like to see next by opening a suggestion on our " + a(href: "https://github.com/ruby-ui/ruby_ui/issues/new", target: "_blank", class: "font-medium underline underline-offset-4") { "GitHub Issues" } + plain " page." + end + end + end + end +end diff --git a/app/views/docs/getting_started/customizing_components.rb b/app/views/docs/getting_started/customizing_components.rb index 4c363f5e1..a839bdf8c 100644 --- a/app/views/docs/getting_started/customizing_components.rb +++ b/app/views/docs/getting_started/customizing_components.rb @@ -61,7 +61,7 @@ def view_template plain "First, find the source code for the component you want to redefine. In this case, we want to redefine the " InlineCode { "Alert" } plain " component, so we'll find the source code for the alert component " - InlineLink(href: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/alert.rb") { "here on Github" } + InlineLink(href: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/alert.rb") { "here on Github" } plain "." end end @@ -122,7 +122,7 @@ def view_template Components.TypographyList(numbered: true) do Components.TypographyListItem do plain "Check the " - InlineLink(href: "https://github.com/PhlexUI/phlex_ui") { "RubyUI GitHub repository" } + InlineLink(href: "https://github.com/ruby-ui/ruby_ui") { "RubyUI GitHub repository" } plain " to see if the component is already planned" end Components.TypographyListItem { "Open an issue or discussion to propose the new component" } @@ -141,7 +141,7 @@ def alert_component_definition <<~RUBY # frozen_string_literal: true - module PhlexUI + module RubyUI class Alert < Base def initialize(variant: nil, **attrs) @variant = variant diff --git a/app/views/docs/getting_started/introduction.rb b/app/views/docs/getting_started/introduction.rb index 1d4b1a2c3..7eaa124de 100644 --- a/app/views/docs/getting_started/introduction.rb +++ b/app/views/docs/getting_started/introduction.rb @@ -2,10 +2,11 @@ class Views::Docs::GettingStarted::Introduction < Views::Base def view_template - div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + div(class: "max-w-2xl mx-auto w-full py-4 space-y-10") do render Docs::Header.new(title: "Introduction", description: "Reusable UI components for Ruby developers") div(class: "space-y-4") do + iframe(width: "100%", height: "400", src: "https://www.youtube.com/embed/OQZam7rug00?si=JmZNzS5u194Q0AWQ", title: "YouTube video player", frameborder: "0", class: "rounded-xl border shadow-lg mb-10", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowfullscreen: true) Heading(level: 2) { "About" } Text do plain "RubyUI is a UI framework for Ruby developers, built on top of " @@ -63,85 +64,97 @@ def view_template end end - div(class: "space-y-4") do - Heading(level: 2) { "Why I built RubyUI" } - Text do - plain "Many Ruby developers are familiar with " - InlineLink(href: "https://rubyonrails.org") { "Rails" } - plain ", and the " - InlineLink(href: "https://guides.rubyonrails.org/layouts_and_rendering.html") { "convention over configuration" } - plain " approach it takes. RubyUI is built on the same principles, providing a set of components that are easy to use, and easy to customize." - end - Text do - plain "RubyUI was born out of a desire for a comprehensive UI framework designed with Ruby developers in mind. While I've previously utilized TailwindUI and other solutions, none seemed to fit just right. The plethora of UI component libraries available for JavaScript frameworks highlighted a gap in the Ruby ecosystem, which RubyUI aims to fill." - end - Text do - plain "Upon discovering Phlex, it became clear that it was the ideal foundation for such a library. It offered the potential for a powerful, easy-to-use, and customizable component library when paired with StimulusJS. The goal was to create a tool that leverages the strengths of TailwindCSS and StimulusJS, providing Ruby developers with a comprehensive UI solution that is stylable at the HTML level." - end - end + div(class: "pt-10 border-t space-y-8") do + Heading(level: 1) { "Notes from the original author" } - div(class: "space-y-4") do - Heading(level: 2) { "Goals of RubyUI" } - Components.TypographyList(numbered: true) do - Components.TypographyListItem { "Create a reusable UI component library specifically for Ruby devs" } - Components.TypographyListItem { "Enable Ruby devs to create custom and complex UIs without needing to write CSS or JS" } + div(class: "flex items-center gap-3") do + Avatar(size: :md, class: "border") { img(src: "https://i.pravatar.cc/150?u=george", alt: "George Kettle") } + div do + p(class: "text-sm font-medium leading-none") { "George Kettle" } + p(class: "text-sm text-muted-foreground") { "Original author of RubyUI" } + end end - end - div(class: "space-y-4") do - Heading(level: 2) { "My experience using Phlex" } - Text do - span(class: "font-medium") { "I was initially skeptical about Phlex. " } - plain "I worried about using an abstraction layer on top of HTML and thought this would be a bad move. However, after trying it I realised that I was wrong, and " - span(class: "font-medium") { "I know others who have had the same experience as myself" } - plain "." - end - Text { "After some time using Phlex, it's obvious to me that this is a better way to render your views in Ruby apps. Phlex is intuitive and simple. It is also incredibly fast (12x faster than ERB, 5x faster than ViewComponent), it also makes your code more organised and leads to a better developer experience." } - Text(size: "4", weight: "semibold") { "Same same, but different" } - Text do - plain "Phlex is essentially just HTML in Ruby form, bundled into a component. It's a simple concept, but it's incredibly powerful. It allows you to write your views in pure Ruby, without the need for a templating language. This means you can use all the features of Ruby, including loops, conditionals, and more." - end - Text do - plain "As an example, if you want to render a " - InlineCode { "

Phlex. Same same, but different.

" } - plain " element, you can do it like this " - InlineCode { "p(class: 'text-sm font-muted-foreground') { 'Phlex. Same same, but different.' }" } - plain "." - end - Text do - plain "This is a simple example, but it's easy to see how this can be scaled up to more complex views. " - span(class: "font-medium") { "It's only natural that we use logic to build HTML" } - plain ". Phlex simplifies this process, making it easier to convert data structures into HTML." + div(class: "space-y-4") do + Heading(level: 2) { "Why I built RubyUI" } + Text do + plain "Many Ruby developers are familiar with " + InlineLink(href: "https://rubyonrails.org") { "Rails" } + plain ", and the " + InlineLink(href: "https://guides.rubyonrails.org/layouts_and_rendering.html") { "convention over configuration" } + plain " approach it takes. RubyUI is built on the same principles, providing a set of components that are easy to use, and easy to customize." + end + Text do + plain "RubyUI was born out of a desire for a comprehensive UI framework designed with Ruby developers in mind. While I've previously utilized TailwindUI and other solutions, none seemed to fit just right. The plethora of UI component libraries available for JavaScript frameworks highlighted a gap in the Ruby ecosystem, which RubyUI aims to fill." + end + Text do + plain "Upon discovering Phlex, it became clear that it was the ideal foundation for such a library. It offered the potential for a powerful, easy-to-use, and customizable component library when paired with StimulusJS. The goal was to create a tool that leverages the strengths of TailwindCSS and StimulusJS, providing Ruby developers with a comprehensive UI solution that is stylable at the HTML level." + end end - end - div(class: "space-y-4") do - Heading(level: 2) { "Acknowledgments" } - Text { "I'd like to thank the following projects and people for helping me build RubyUI" } - Components.TypographyList do - Components.TypographyListItem do - InlineLink(href: "https://github.com/joeldrapper") { "Joel Drapper" } - plain " - Thanks for creating Phlex, and for your support and advice." + div(class: "space-y-4") do + Heading(level: 2) { "Goals of RubyUI" } + Components.TypographyList(numbered: true) do + Components.TypographyListItem { "Create a reusable UI component library specifically for Ruby devs" } + Components.TypographyListItem { "Enable Ruby devs to create custom and complex UIs without needing to write CSS or JS" } end - Components.TypographyListItem do - InlineLink(href: "https://phlex.fun") { "Phlex" } - plain " - The foundation of RubyUI." + end + + div(class: "space-y-4") do + Heading(level: 2) { "My experience using Phlex" } + Text do + span(class: "font-medium") { "I was initially skeptical about Phlex. " } + plain "I worried about using an abstraction layer on top of HTML and thought this would be a bad move. However, after trying it I realised that I was wrong, and " + span(class: "font-medium") { "I know others who have had the same experience as myself" } + plain "." end - Components.TypographyListItem do - InlineLink(href: "https://ui.shadcn.com") { "shadcn/ui" } - plain " - The design inspiration for RubyUI's components and theming system." + Text { "After some time using Phlex, it's obvious to me that this is a better way to render your views in Ruby apps. Phlex is intuitive and simple. It is also incredibly fast (12x faster than ERB, 5x faster than ViewComponent), it also makes your code more organised and leads to a better developer experience." } + Text(size: "4", weight: "semibold") { "Same same, but different" } + Text do + plain "Phlex is essentially just HTML in Ruby form, bundled into a component. It's a simple concept, but it's incredibly powerful. It allows you to write your views in pure Ruby, without the need for a templating language. This means you can use all the features of Ruby, including loops, conditionals, and more." end - Components.TypographyListItem do - InlineLink(href: "https://stimulus.hotwired.dev") { "Stimulus JS" } - plain " - A quicker way to write JavaScript." + Text do + plain "As an example, if you want to render a " + InlineCode { "

Phlex. Same same, but different.

" } + plain " element, you can do it like this " + InlineCode { "p(class: 'text-sm font-muted-foreground') { 'Phlex. Same same, but different.' }" } + plain "." end - Components.TypographyListItem do - InlineLink(href: "http://tailwindcss.com") { "TailwindCSS" } - plain " - I wouldn't build without it." + Text do + plain "This is a simple example, but it's easy to see how this can be scaled up to more complex views. " + span(class: "font-medium") { "It's only natural that we use logic to build HTML" } + plain ". Phlex simplifies this process, making it easier to convert data structures into HTML." end - Components.TypographyListItem do - InlineLink(href: "https://twitter.com/george_kettle") { "My Twitter followers" } - plain " - Thanks for all the ideas, feedback and support." + end + + div(class: "space-y-4") do + Heading(level: 2) { "Acknowledgments" } + Text { "I'd like to thank the following projects and people for helping me build RubyUI" } + Components.TypographyList do + Components.TypographyListItem do + InlineLink(href: "https://github.com/joeldrapper") { "Joel Drapper" } + plain " - Thanks for creating Phlex, and for your support and advice." + end + Components.TypographyListItem do + InlineLink(href: "https://phlex.fun") { "Phlex" } + plain " - The foundation of RubyUI." + end + Components.TypographyListItem do + InlineLink(href: "https://ui.shadcn.com") { "shadcn/ui" } + plain " - The design inspiration for RubyUI's components and theming system." + end + Components.TypographyListItem do + InlineLink(href: "https://stimulus.hotwired.dev") { "Stimulus JS" } + plain " - A quicker way to write JavaScript." + end + Components.TypographyListItem do + InlineLink(href: "http://tailwindcss.com") { "TailwindCSS" } + plain " - I wouldn't build without it." + end + Components.TypographyListItem do + InlineLink(href: "https://twitter.com/george_kettle") { "My Twitter followers" } + plain " - Thanks for all the ideas, feedback and support." + end end end end diff --git a/app/views/docs/hover_card.rb b/app/views/docs/hover_card.rb index 1aa18ed55..3512d127e 100644 --- a/app/views/docs/hover_card.rb +++ b/app/views/docs/hover_card.rb @@ -62,10 +62,10 @@ def view_template # 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) + # Docs::ComponentStruct.new(name: "PopoverController", source: "https://github.com/ruby-ui/ruby_ui_stimulus/blob/main/controllers/popover_controller.js", built_using: :stimulus), + # Docs::ComponentStruct.new(name: "HoverCard", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/hover_card.rb", built_using: :phlex), + # Docs::ComponentStruct.new(name: "HoverCardTrigger", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/hover_card/trigger.rb", built_using: :phlex), + # Docs::ComponentStruct.new(name: "HoverCardContent", source: "https://github.com/ruby-ui/ruby_ui/blob/main/lib/ruby_ui/hover_card/content.rb", built_using: :phlex) # ] # end end diff --git a/app/views/docs/native_select.rb b/app/views/docs/native_select.rb new file mode 100644 index 000000000..98a6a76e8 --- /dev/null +++ b/app/views/docs/native_select.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +class Views::Docs::NativeSelect < Views::Base + def view_template + component = "NativeSelect" + + div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do + render Docs::Header.new(title: "Native Select", description: "A styled native HTML select element with consistent design system integration.") + + Heading(level: 2) { "Usage" } + + render Docs::VisualCodeExample.new(title: "Default", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + NativeSelect do + NativeSelectOption(value: "") { "Select a fruit" } + NativeSelectOption(value: "apple") { "Apple" } + NativeSelectOption(value: "banana") { "Banana" } + NativeSelectOption(value: "blueberry") { "Blueberry" } + NativeSelectOption(value: "pineapple") { "Pineapple" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Groups", description: "Use NativeSelectGroup to organize options into categories.", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + NativeSelect do + NativeSelectOption(value: "") { "Select a department" } + NativeSelectGroup(label: "Engineering") do + NativeSelectOption(value: "frontend") { "Frontend" } + NativeSelectOption(value: "backend") { "Backend" } + NativeSelectOption(value: "devops") { "DevOps" } + end + NativeSelectGroup(label: "Sales") do + NativeSelectOption(value: "account_executive") { "Account Executive" } + NativeSelectOption(value: "sales_development") { "Sales Development" } + end + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Disabled", description: "Add the disabled attribute to the NativeSelect component to disable the select.", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + NativeSelect(disabled: true) do + NativeSelectOption(value: "") { "Select a fruit" } + NativeSelectOption(value: "apple") { "Apple" } + NativeSelectOption(value: "banana") { "Banana" } + NativeSelectOption(value: "blueberry") { "Blueberry" } + end + end + RUBY + end + + render Docs::VisualCodeExample.new(title: "Invalid", description: "Use aria-invalid to show validation errors.", context: self) do + <<~RUBY + div(class: "grid w-full max-w-sm items-center gap-1.5") do + NativeSelect(aria: {invalid: "true"}) do + NativeSelectOption(value: "") { "Select a fruit" } + NativeSelectOption(value: "apple") { "Apple" } + NativeSelectOption(value: "banana") { "Banana" } + NativeSelectOption(value: "blueberry") { "Blueberry" } + end + end + RUBY + end + + Heading(level: 2) { "Native Select vs Select" } + + div(class: "space-y-2 text-sm text-muted-foreground") do + p { "NativeSelect: Choose for native browser behavior, superior performance, or mobile-optimized dropdowns." } + p { "Select: Choose for custom styling, animations, or complex interactions." } + end + + render Components::ComponentSetup::Tabs.new(component_name: component) + + render Docs::ComponentsTable.new(component_files(component)) + end + end +end diff --git a/app/views/layouts/application_layout.rb b/app/views/layouts/application_layout.rb index 80a886c5b..252d2ec8c 100644 --- a/app/views/layouts/application_layout.rb +++ b/app/views/layouts/application_layout.rb @@ -15,6 +15,7 @@ def view_template(&block) script(defer: true, data_domain: "rubyui.com", src: "https://plausible.io/js/script.js") render Shared::Navbar.new main(&block) + render Shared::Footer.new render Shared::Flashes.new(notice: flash[:notice], alert: flash[:alert]) end end diff --git a/app/views/layouts/docs_layout.rb b/app/views/layouts/docs_layout.rb index 8d94ea90e..85fe4adc3 100644 --- a/app/views/layouts/docs_layout.rb +++ b/app/views/layouts/docs_layout.rb @@ -21,6 +21,7 @@ def view_template(&block) end end end + render Shared::Footer.new render Shared::Flashes.new(notice: flash[:notice], alert: flash[:alert]) end end diff --git a/app/views/layouts/pages_layout.rb b/app/views/layouts/pages_layout.rb index 98bfc95ef..9bd8c1ec6 100644 --- a/app/views/layouts/pages_layout.rb +++ b/app/views/layouts/pages_layout.rb @@ -14,6 +14,7 @@ def view_template(&block) body do render Shared::Navbar.new main(class: "relative", &block) + render Shared::Footer.new render Shared::Flashes.new(notice: flash[:notice], alert: flash[:alert]) end end diff --git a/app/views/pages/home.rb b/app/views/pages/home.rb index e476ffbf6..88deffb8b 100644 --- a/app/views/pages/home.rb +++ b/app/views/pages/home.rb @@ -2,216 +2,287 @@ class Views::Pages::Home < Views::Base def view_template - render HomeView::Banner.new do |banner| - banner.cta do - Link(variant: :outline, href: docs_accordion_path, class: "text-center justify-center") { "Browse Components" } - Link(variant: :primary, href: docs_introduction_path, class: "text-center justify-center") do - plain "Get Started" - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 20 20", - fill: "currentColor", - class: "w-5 h-5 ml-1 -mr-1" - ) do |s| - s.path( - fill_rule: "evenodd", - d: - "M5 10a.75.75 0 01.75-.75h6.638L10.23 7.29a.75.75 0 111.04-1.08l3.5 3.25a.75.75 0 010 1.08l-3.5 3.25a.75.75 0 11-1.04-1.08l2.158-1.96H5.75A.75.75 0 015 10z", - clip_rule: "evenodd" - ) - end + # Hero Section + section(class: "space-y-6 pt-6 md:pt-10 lg:pt-16") do + div(class: "container flex max-w-[64rem] flex-col items-center gap-4 text-center mx-auto") do + Link(href: docs_changelog_path, variant: :outline, class: "rounded-2xl px-4 py-1.5 text-sm font-medium") do + span(class: "sm:hidden") { "New components available" } + span(class: "hidden sm:inline") { "Combobox updates and more" } + 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: "ml-2 h-4 w-4") { |s| + s.path(d: "M5 12h14") + s.path(d: "m12 5 7 7-7 7") + } + end + h1(class: "leading-tighter text-3xl font-semibold tracking-tight text-balance text-primary lg:leading-[1.1] lg:font-semibold xl:text-5xl xl:tracking-tighter max-w-4xl") do + [ + "Build sharp Ruby interfaces.", + "Reusable UI components for Ruby developers.", + "Pure Ruby UI, built with Phlex.", + "Elevate your Rails apps with RubyUI.", + "The sharpest way to build in Ruby." + ].sample + end + p(class: "max-w-4xl text-base text-balance text-foreground sm:text-lg") do + "A UI component library, crafted precisely for Ruby devs who want to stay organised and build modern apps, fast." + end + div(class: "space-x-4 mt-4") do + Link(href: docs_introduction_path, variant: :primary, size: :lg) { "Documentation" } + Link(href: docs_components_path, variant: :outline, size: :lg) { "View Components" } end end end - div(class: "overflow-hidden") do - div(class: "container mx-auto max-w-5xl px-4 flex justify-center my-8") do - iframe(width: "100%", height: "720", src: "https://www.youtube.com/embed/OQZam7rug00?si=JmZNzS5u194Q0AWQ", title: "YouTube video player", frameborder: "0", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", referrerpolicy: "strict-origin-when-cross-origin", allowfullscreen: true) - end - - div(class: "relative z-10 container mx-auto max-w-5xl pt-16 lg:pt-16 py-24 px-4") do - div(class: "grid grid-cols-6 gap-4") do - render HomeView::Card.new(class: "col-span-6 sm:col-span-3 md:col-span-4", title: "Built for Speed", subtitle: "Dive into a world where your Rails UI development happens at light speed. Phlex is not just fast - it's blazing fast.", color: :secondary) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - fill_rule: "evenodd", - d: - "M12.963 2.286a.75.75 0 00-1.071-.136 9.742 9.742 0 00-3.539 6.177A7.547 7.547 0 016.648 6.61a.75.75 0 00-1.152-.082A9 9 0 1015.68 4.534a7.46 7.46 0 01-2.717-2.248zM15.75 14.25a3.75 3.75 0 11-7.313-1.172c.628.465 1.35.81 2.133 1a5.99 5.99 0 011.925-3.545 3.75 3.75 0 013.255 3.717z", - clip_rule: "evenodd" - ) - end + # Components Mosaic Section + section(class: "container py-8 md:py-12 lg:py-24 mx-auto max-w-6xl") do + div(class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6") do + # COLUMN 1 + div(class: "space-y-6") do + # PAYMENT CARD + Card do + CardHeader(class: "space-y-1") do + CardTitle { "Payment Method" } + CardDescription { "All transactions are secure and encrypted." } end - end - render HomeView::Card.new(class: "col-span-6 sm:col-span-3 md:col-span-2", color: :sky) do |card| - card.content do - div(class: "flex flex-col items-center justify-center text-center space-y-4 h-full") do - p(class: "text-6xl font-semibold") { "7.7x" } - p do - a(href: "https://github.com/palkan/view-layer-benchmarks", class: "underline") { "Faster" } - span { " than traditional Rails ERB" } + CardContent(class: "grid gap-4") do + div(class: "grid gap-2") do + Label { "Name on Card" } + Input(placeholder: "John Doe") + end + div(class: "grid gap-2") do + Label { "Card Number" } + Input(placeholder: "1234 5678 9012 3456") + end + div(class: "grid grid-cols-3 gap-4") do + div(class: "grid gap-2 col-span-2") do + Label { "Expires" } + div(class: "grid grid-cols-2 gap-2") do + Button(variant: :outline, class: "justify-between text-muted-foreground font-normal") do + span { "Month" } + lucide_icon "chevron-down", class: "h-3 w-3 opacity-50" + end + Button(variant: :outline, class: "justify-between text-muted-foreground font-normal") do + span { "Year" } + lucide_icon "chevron-down", class: "h-3 w-3 opacity-50" + end + end end + div(class: "grid gap-2") do + Label { "CVV" } + Input(placeholder: "CVV") + end + end + div(class: "flex items-center space-x-2 pt-2") do + Checkbox(id: "shipping") + Label(for: "shipping", class: "text-sm font-normal") { "Same as shipping address" } end end + CardFooter(class: "flex justify-between gap-2") do + Button(variant: :outline, class: "flex-1") { "Cancel" } + Button(class: "flex-1") { "Pay Now" } + end end - render HomeView::Card.new(class: "col-span-6 sm:col-span-3", title: "UI that... Wow!", subtitle: "Design stunning, streamlined, and customizable UIs that not only look great but sell your app without you lifting a finger.", color: :violet) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - fill_rule: "evenodd", - d: - "M9 4.5a.75.75 0 01.721.544l.813 2.846a3.75 3.75 0 002.576 2.576l2.846.813a.75.75 0 010 1.442l-2.846.813a3.75 3.75 0 00-2.576 2.576l-.813 2.846a.75.75 0 01-1.442 0l-.813-2.846a3.75 3.75 0 00-2.576-2.576l-2.846-.813a.75.75 0 010-1.442l2.846-.813A3.75 3.75 0 007.466 7.89l.813-2.846A.75.75 0 019 4.5zM18 1.5a.75.75 0 01.728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 010 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 01-1.456 0l-.258-1.036a2.625 2.625 0 00-1.91-1.91l-1.036-.258a.75.75 0 010-1.456l1.036-.258a2.625 2.625 0 001.91-1.91l.258-1.036A.75.75 0 0118 1.5zM16.5 15a.75.75 0 01.712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 010 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 01-1.422 0l-.395-1.183a1.5 1.5 0 00-.948-.948l-1.183-.395a.75.75 0 010-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0116.5 15z", - clip_rule: "evenodd" - ) - end + + # ALERT Showcase + Alert do + lucide_icon "terminal", class: "h-4 w-4" + AlertTitle { "Heads up!" } + AlertDescription { "You can add components directly to your app using Phlex." } + end + + # ACTIVITY FEED + Card do + CardHeader(class: "pb-3") do + CardTitle { "Activity Feed" } + end + CardContent(class: "flex flex-wrap gap-2") do + Badge(variant: :sky) { "In Review" } + Badge(variant: :success) { "Ready to Ship" } + Badge(variant: :outline) { "Draft" } + Badge(variant: :destructive) { "Rejected" } + Badge(variant: :primary) { "Deployed" } + Badge(variant: :amber) { "Agent Thinking" } end end - render HomeView::Card.new(class: "col-span-6 sm:col-span-3", title: "Stay Organized", subtitle: "Say goodbye to clutter. With Phlex, your UI components are not only organized, but also easy to manage and track.", color: :secondary) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - d: - "M21 6.375c0 2.692-4.03 4.875-9 4.875S3 9.067 3 6.375 7.03 1.5 12 1.5s9 2.183 9 4.875z" - ) - s.path( - d: - "M12 12.75c2.685 0 5.19-.586 7.078-1.609a8.283 8.283 0 001.897-1.384c.016.121.025.244.025.368C21 12.817 16.97 15 12 15s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.285 8.285 0 001.897 1.384C6.809 12.164 9.315 12.75 12 12.75z" - ) - s.path( - d: - "M12 16.5c2.685 0 5.19-.586 7.078-1.609a8.282 8.282 0 001.897-1.384c.016.121.025.244.025.368 0 2.692-4.03 4.875-9 4.875s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.284 8.284 0 001.897 1.384C6.809 15.914 9.315 16.5 12 16.5z" - ) - s.path( - d: - "M12 20.25c2.685 0 5.19-.586 7.078-1.609a8.282 8.282 0 001.897-1.384c.016.121.025.244.025.368 0 2.692-4.03 4.875-9 4.875s-9-2.183-9-4.875c0-.124.009-.247.025-.368a8.284 8.284 0 001.897 1.384C6.809 19.664 9.315 20.25 12 20.25z" - ) + end + + # COLUMN 2 + div(class: "space-y-6") do + # TABS + Tabs(default_value: "account") do + TabsList(class: "grid w-full grid-cols-2") do + TabsTrigger(value: "account") { "Account" } + TabsTrigger(value: "password") { "Password" } + end + TabsContent(value: "account") do + Card do + CardHeader do + CardTitle { "Account" } + CardDescription { "Make changes to your account here." } + end + CardContent(class: "space-y-2") do + div(class: "space-y-1") do + Label { "Username" } + Input(placeholder: "@djalma") + end + div(class: "space-y-1") do + Label { "Email" } + Input(placeholder: "djalma@nossomos.cc") + end + end + CardFooter do + Button(size: :sm, class: "w-full") { "Save changes" } + end end end end - div(class: "relative col-span-6") do - render HomeView::Shapes.new(color: :violet, class: "hidden md:block absolute top-0 left-0 rotate-90 -translate-x-1/2 translate-y-full", size: :lg) - div(class: "mx-auto max-w-lg py-28") do - steps = [ - { - title: "Find the perfect component", - description: "Each component is embedded live on the page so you can find exactly the design you want." - }, - { - title: "Copy the snippet", - description: "Click the \"Code\" tab to see the code for a component and grab the part that you need, or click the clipboard button to quickly copy the entire snippet in one step." - }, - { - title: "Make it yours", - description: "Every component is built entirely out of Tailwind utility classes, so you can easily dive in and adjust anything you want to better fit your use case." - } - ] - render HomeView::Steps.new(steps: steps) + + # TEAM MEMBERS + Card do + CardHeader do + CardTitle { "Team Members" } + CardDescription { "Collaborate with your team." } + end + CardContent(class: "space-y-6") do + team_member("Sofia Davis", "@sdavis", "https://i.pravatar.cc/150?u=sofia") + team_member("Jackson Lee", "@jlee", "https://i.pravatar.cc/150?u=jackson") + team_member("Djalma Araújo", "@djalma", "https://i.pravatar.cc/150?u=djalma") + team_member("George Kettle", "@gkettle", "https://i.pravatar.cc/150?u=george") + end + CardFooter do + Button(variant: :outline, class: "w-full") { "Invite Members" } end end - render HomeView::Card.new(class: "col-span-6 md:col-span-4", title: "Customer-Centric UX", subtitle: "Create an app experience your users will rave about. RubyUI ensures that your user's journey is nothing less than memorable.", color: :pink) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - d: - "M4.5 6.375a4.125 4.125 0 118.25 0 4.125 4.125 0 01-8.25 0zM14.25 8.625a3.375 3.375 0 116.75 0 3.375 3.375 0 01-6.75 0zM1.5 19.125a7.125 7.125 0 0114.25 0v.003l-.001.119a.75.75 0 01-.363.63 13.067 13.067 0 01-6.761 1.873c-2.472 0-4.786-.684-6.76-1.873a.75.75 0 01-.364-.63l-.001-.122zM17.25 19.128l-.001.144a2.25 2.25 0 01-.233.96 10.088 10.088 0 005.06-1.01.75.75 0 00.42-.643 4.875 4.875 0 00-6.957-4.611 8.586 8.586 0 011.71 5.157v.003z" - ) + end + + # COLUMN 3 + div(class: "space-y-6") do + # PROGRESS / QUOTA + Card do + CardHeader(class: "pb-4") do + div(class: "flex items-center justify-between") do + CardTitle { "Storage Usage" } + span(class: "text-xs text-muted-foreground") { "12.4GB / 20GB" } end end + CardContent do + Progress(value: 62) + end + CardFooter do + Button(variant: :ghost, size: :sm, class: "w-full text-xs") { "Upgrade Plan" } + end end - render HomeView::Card.new(class: "col-span-6 md:col-span-2", title: "Completely Customisable", subtitle: "Have full control over the design of all components.", color: :amber) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - fill_rule: "evenodd", - d: - "M20.599 1.5c-.376 0-.743.111-1.055.32l-5.08 3.385a18.747 18.747 0 00-3.471 2.987 10.04 10.04 0 014.815 4.815 18.748 18.748 0 002.987-3.472l3.386-5.079A1.902 1.902 0 0020.599 1.5zm-8.3 14.025a18.76 18.76 0 001.896-1.207 8.026 8.026 0 00-4.513-4.513A18.75 18.75 0 008.475 11.7l-.278.5a5.26 5.26 0 013.601 3.602l.502-.278zM6.75 13.5A3.75 3.75 0 003 17.25a1.5 1.5 0 01-1.601 1.497.75.75 0 00-.7 1.123 5.25 5.25 0 009.8-2.62 3.75 3.75 0 00-3.75-3.75z", - clip_rule: "evenodd" - ) + + # SETTINGS / SWITCHES + Card do + CardHeader do + CardTitle { "Settings" } + CardDescription { "Manage your preferences." } + end + CardContent(class: "space-y-4") do + div(class: "flex items-center justify-between rounded-lg border p-4 shadow-sm") do + div(class: "space-y-0.5") do + p(class: "font-medium text-sm") { "Kubernetes" } + p(class: "text-xs text-muted-foreground") { "Highly available cluster." } + end + Switch(checked: true) + end + div(class: "flex items-center justify-between rounded-lg border p-4 shadow-sm") do + div(class: "space-y-0.5") do + p(class: "font-medium text-sm") { "Dark Mode" } + p(class: "text-xs text-muted-foreground") { "Use the dark theme." } + end + Switch() end end end - render HomeView::Card.new(class: "col-span-6 sm:col-span-3", title: "Minimal Dependencies", subtitle: "Keep your app lean and mean. With RubyUI, we use custom built Stimulus.js controllers wherever possible - less package dependencies to worry about.", color: :secondary) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - d: - "M7.493 18.75c-.425 0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H14.23c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23h-.777zM2.331 10.977a11.969 11.969 0 00-.831 4.398 12 12 0 00.52 3.507c.26.85 1.084 1.368 1.973 1.368H4.9c.445 0 .72-.498.523-.898a8.963 8.963 0 01-.924-3.977c0-1.708.476-3.305 1.302-4.666.245-.403-.028-.959-.5-.959H4.25c-.832 0-1.612.453-1.918 1.227z" - ) + + # AI AGENT CHAT + Card do + CardHeader(class: "pb-2") do + div(class: "flex items-center justify-between") do + CardTitle { "AI Assistant" } + Badge(variant: :secondary, size: :sm) { "v4.0" } end end - end - render HomeView::Card.new(class: "col-span-6 sm:col-span-3", title: "Reuse with Ease", subtitle: "Avoid the hassle of constantly reconstructing components. With Phlex, once built, use them seamlessly as needed.", color: :lime) do |card| - card.icon do - svg( - xmlns: "http://www.w3.org/2000/svg", - viewbox: "0 0 24 24", - fill: "currentColor", - class: "w-6 h-6" - ) do |s| - s.path( - fill_rule: "evenodd", - d: - "M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z", - clip_rule: "evenodd" - ) + CardContent(class: "space-y-4") do + div(class: "max-w-[80%] rounded-lg bg-muted p-3 text-sm") do + "How can I help you build with Ruby today?" + end + div(class: "flex flex-wrap gap-2") do + Button(variant: :outline, size: :sm, class: "h-7 text-[10px] gap-1") do + lucide_icon "plus", class: "h-3 w-3" + span { "Add Context" } + end + Button(variant: :outline, size: :sm, class: "h-7 text-[10px] gap-1") do + lucide_icon "globe", class: "h-3 w-3" + span { "Web Search" } + end + end + end + CardFooter(class: "flex flex-col gap-3 pt-0") do + div(class: "flex items-center w-full gap-2") do + Button(variant: :ghost, size: :icon, class: "h-8 w-8 shrink-0") do + lucide_icon "plus-circle", class: "h-4 w-4" + end + div(class: "relative flex-1") do + Input(placeholder: "Ask anything...", class: "pr-10 h-10") + Button(variant: :primary, size: :icon, class: "absolute right-1 top-1 h-8 w-8") do + lucide_icon "arrow-up", class: "h-4 w-4" + end + end + end + div(class: "flex items-center gap-2 text-[10px] text-muted-foreground") do + lucide_icon "zap", class: "h-3 w-3" + span { "GPT-4o" } + span(class: "mx-1") { "•" } + lucide_icon "layers", class: "h-3 w-3" + span { "Professional Plan" } end end end end end - div(class: "relative z-0 h-72 rotate-180 -mt-56 -mr-px") do - render Shared::GridPattern.new - end - div(class: "relative h-72") do - render Shared::GridPattern.new + end + end + + private + + def Label(class: nil, **attrs, &block) + base_classes = "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" + + label(class: [base_classes, binding.local_variable_get(:class)], **attrs, &block) + end + + def team_member(name, handle, avatar_url) + div(class: "flex items-center justify-between") do + div(class: "flex items-center gap-4") do + Avatar do + AvatarImage(src: avatar_url, alt: name) + AvatarFallback { name.split.map(&:first).join } + end + div do + p(class: "text-sm font-medium leading-none") { name } + p(class: "text-xs text-muted-foreground") { handle } + end end + Button(variant: :ghost, size: :sm) { "Edit" } end end - def speed_tests - [ - { - framework: "Phlex", - time: 1 - }, - { - framework: "ViewComponent", - time: 5.57 - }, - { - framework: "ERB Templates", - time: 12.08 - } - ].sort_by { |test| test[:time] } + def activity_item(title, time, status) + div(class: "flex items-center gap-4") do + div(class: [ + "h-2 w-2 rounded-full", + (if status == :success + "bg-green-500" + else + ((status == :warning) ? "bg-amber-500" : "bg-blue-500") + end) + ]) + div do + p(class: "text-sm font-medium") { title } + p(class: "text-xs text-muted-foreground") { time } + end + end end end diff --git a/config/routes.rb b/config/routes.rb index a6da0ff23..bee400407 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,8 @@ get "installation/rails_importmaps", to: "docs#installation_rails_importmaps", as: :docs_installation_rails_importmaps # COMPONENTS + get "components", to: "docs#components", as: :docs_components + get "changelog", to: "docs#changelog", as: :docs_changelog get "accordion", to: "docs#accordion", as: :docs_accordion get "alert", to: "docs#alert_component", as: :docs_alert # alert is a reserved word for controller action get "alert_dialog", to: "docs#alert_dialog", as: :docs_alert_dialog @@ -46,6 +48,7 @@ get "popover", to: "docs#popover", as: :docs_popover get "progress", to: "docs#progress", as: :docs_progress get "radio_button", to: "docs#radio_button", as: :docs_radio_button + get "native_select", to: "docs#native_select", as: :docs_native_select get "select", to: "docs#select", as: :docs_select get "separator", to: "docs#separator", as: :docs_separator get "sheet", to: "docs#sheet", as: :docs_sheet