diff --git a/Gemfile b/Gemfile
index a3624f1..bb3d24c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,7 +20,10 @@ gem "stimulus-rails", "1.3.3"
# Bundle and process CSS [https://github.com/rails/cssbundling-rails]
gem "cssbundling-rails", "1.4.0"
-# Build JSON APIs with ease [https://github.com/rails/jbuilder]
+gem "lookbook", "2.3.2"
+
+gem "lucide-rails", "0.4.0"
+
# gem "jbuilder"
# Use Redis adapter to run Action Cable in production
diff --git a/Gemfile.lock b/Gemfile.lock
index 8c68d2f..e5dd074 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -102,6 +102,8 @@ GEM
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
crass (1.0.6)
+ css_parser (1.17.1)
+ addressable
cssbundling-rails (1.4.0)
railties (>= 6.0.0)
date (3.3.4)
@@ -114,6 +116,8 @@ GEM
erubi (1.13.0)
globalid (1.2.1)
activesupport (>= 6.1)
+ htmlbeautifier (1.4.3)
+ htmlentities (4.3.4)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
io-console (0.7.2)
@@ -129,6 +133,20 @@ GEM
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
+ lookbook (2.3.2)
+ activemodel
+ css_parser
+ htmlbeautifier (~> 1.3)
+ htmlentities (~> 4.3.4)
+ marcel (~> 1.0)
+ railties (>= 5.0)
+ redcarpet (~> 3.5)
+ rouge (>= 3.26, < 5.0)
+ view_component (>= 2.0)
+ yard (~> 0.9)
+ zeitwerk (~> 2.5)
+ lucide-rails (0.4.0)
+ railties (>= 4.1.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
@@ -136,6 +154,7 @@ GEM
net-smtp
marcel (1.0.4)
matrix (0.4.2)
+ method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.24.1)
msgpack (1.7.2)
@@ -222,6 +241,7 @@ GEM
rake (13.2.1)
rdoc (6.7.0)
psych (>= 4.0.0)
+ redcarpet (3.6.0)
regexp_parser (2.9.2)
reline (0.5.9)
io-console (~> 0.5)
@@ -284,6 +304,10 @@ GEM
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
useragent (0.16.10)
+ view_component (3.13.0)
+ activesupport (>= 5.2.0, < 8.0)
+ concurrent-ruby (~> 1.0)
+ method_source (~> 1.0)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
@@ -296,6 +320,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
+ yard (0.9.36)
zeitwerk (2.6.16)
PLATFORMS
@@ -313,6 +338,8 @@ DEPENDENCIES
debug
dockerfile-rails (>= 1.6)
jsbundling-rails (= 1.3.0)
+ lookbook (= 2.3.2)
+ lucide-rails (= 0.4.0)
phlex-rails
phlex_ui!
propshaft (= 0.9.0)
diff --git a/app/views/components/shared/navbar.rb b/app/views/components/shared/navbar.rb
index 9b8b323..964d281 100644
--- a/app/views/components/shared/navbar.rb
+++ b/app/views/components/shared/navbar.rb
@@ -11,6 +11,7 @@ def view_template
Link(href: helpers.docs_introduction_path, variant: :ghost, class: "hidden md:inline-block") { "Docs" }
Link(href: helpers.docs_accordion_path, variant: :ghost, class: "hidden md:inline-block") { "Components" }
Link(href: helpers.theme_path("default"), variant: :ghost, class: "hidden md:inline-block") { "Themes" }
+ Link(href: helpers.lookbook_path("default"), target: "_blank", variant: :ghost, class: "hidden md:inline-block") { "Lookbook" }
end
div(class: "flex items-center gap-x-2 md:divide-x") do
div(class: "flex items-center") do
diff --git a/app/views/components/test_view.rb b/app/views/components/test_view.rb
new file mode 100644
index 0000000..4091b96
--- /dev/null
+++ b/app/views/components/test_view.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class TestView < Phlex::HTML
+ include PhlexUI
+
+ def view_template(&)
+ div(&)
+ end
+end
diff --git a/app/views/layouts/component_preview.html.erb b/app/views/layouts/component_preview.html.erb
new file mode 100644
index 0000000..e907bc0
--- /dev/null
+++ b/app/views/layouts/component_preview.html.erb
@@ -0,0 +1,18 @@
+
+">
+
+ Component Preview
+
+ <%= stylesheet_link_tag 'application' %>
+ <%= javascript_include_tag 'application', defer: true %>
+
+
+
+ ">
+
+ <%= yield %>
+
+
+
+
diff --git a/config/application.rb b/config/application.rb
index f576ed6..89e8584 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -29,6 +29,10 @@ class Application < Rails::Application
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
+ # lookbook configuration
+ config.lookbook.project_name = "Phlex Components"
+ config.lookbook.preview_layout = "component_preview"
+
config.exceptions_app = routes # redirects all exceptions to custom error pages (See routes)
end
end
diff --git a/config/initializers/lookbook.rb b/config/initializers/lookbook.rb
new file mode 100644
index 0000000..1b52b5d
--- /dev/null
+++ b/config/initializers/lookbook.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# lookbook workaround for phlex components
+if defined?(Lookbook)
+ module Lookbook::PreviewOverrides
+ # see https://github.com/ViewComponent/lookbook/issues/584
+ def render(component = nil, **args, &block)
+ if block
+ super { component.instance_exec component, &block }
+ else
+ super
+ end
+ end
+ end
+
+ Rails.application.configure { Lookbook::Preview.prepend Lookbook::PreviewOverrides }
+end
diff --git a/config/routes.rb b/config/routes.rb
index 8c88aa4..015a47f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,8 @@
Rails.application.routes.draw do
get "themes/:theme", to: "themes#show", as: :theme
+ mount Lookbook::Engine, at: "/lookbook"
+
scope "docs" do
# GETTING STARTED
get "introduction", to: "docs#introduction", as: :docs_introduction
diff --git a/test/components/previews/phlex_ui/accordion_preview.rb b/test/components/previews/phlex_ui/accordion_preview.rb
new file mode 100644
index 0000000..e08b2b2
--- /dev/null
+++ b/test/components/previews/phlex_ui/accordion_preview.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class AccordionPreview < Lookbook::Preview
+ ITEMS = [
+ {title: "Item 1", content: "Content 1"},
+ {title: "Item 2", content: "Content 2"},
+ {title: "Item 3", content: "Content 3"}
+ ]
+
+ # Default Accordion
+ # ---------------
+ def default
+ render(TestView.new) do
+ Accordion do
+ ITEMS.each do |it|
+ AccordionItem do
+ AccordionTrigger { AccordionDefaultTrigger { AccordionIcon { it[:title] } } }
+ AccordionContent { AccordionDefaultContent { it[:content] } }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/alert_dialog_preview.rb b/test/components/previews/phlex_ui/alert_dialog_preview.rb
new file mode 100644
index 0000000..e402720
--- /dev/null
+++ b/test/components/previews/phlex_ui/alert_dialog_preview.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class AlertDialogPreview < Lookbook::Preview
+ # Default AlertDialog
+ # ---------------
+ def default
+ render(TestView.new) do
+ AlertDialog do
+ AlertDialogTrigger { Button { "Show dialog" } }
+
+ AlertDialogContent do
+ AlertDialogHeader do
+ AlertDialogTitle { "Are you absolutely sure?" }
+ AlertDialogDescription do
+ "This action cannot be undone. This will permanently delete your account and remove your data from our servers."
+ end
+ end
+
+ AlertDialogFooter do
+ AlertDialogCancel { "Cancel" }
+ AlertDialogAction { "Continue" }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/alert_preview.rb b/test/components/previews/phlex_ui/alert_preview.rb
new file mode 100644
index 0000000..f62ee1b
--- /dev/null
+++ b/test/components/previews/phlex_ui/alert_preview.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class AlertPreview < Lookbook::Preview
+ # Default Alert
+ # ---------------
+ # @param variant [Symbol] select { choices: [nil, warning, success, destructive] }
+ def variants(variant: nil)
+ render(TestView.new) do
+ Alert(variant:) do
+ AlertTitle { "Pro tip" }
+ AlertDescription { "With PhlexUI you'll ship faster." }
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/aspect_ratio_preview.rb b/test/components/previews/phlex_ui/aspect_ratio_preview.rb
new file mode 100644
index 0000000..1413ddf
--- /dev/null
+++ b/test/components/previews/phlex_ui/aspect_ratio_preview.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class AspectRatioPreview < Lookbook::Preview
+ # AspectRatio 16/9
+ # ---------------
+ def aspect_16x9
+ render(TestView.new) do
+ div(class: "w-96") do
+ AspectRatio(aspect_ratio: "16/9") do |aspect_ratio|
+ aspect_ratio.img(alt: "Placeholder", loading: "lazy", src: helpers.image_path("pattern.jpg"))
+ end
+ end
+ end
+ end
+
+ # AspectRatio 4/3
+ def aspect_4x3
+ render(TestView.new) do
+ div(class: "w-96") do
+ AspectRatio(aspect_ratio: "4/3") do |aspect_ratio|
+ aspect_ratio.img(alt: "Placeholder", loading: "lazy", src: helpers.image_path("pattern.jpg"))
+ end
+ end
+ end
+ end
+
+ # AspectRatio 1/1
+ def aspect_1x1
+ render(TestView.new) do
+ div(class: "w-96") do
+ AspectRatio(aspect_ratio: "1/1") do |aspect_ratio|
+ aspect_ratio.img(alt: "Placeholder", loading: "lazy", src: helpers.image_path("pattern.jpg"))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/avatar_preview.rb b/test/components/previews/phlex_ui/avatar_preview.rb
new file mode 100644
index 0000000..0c22d61
--- /dev/null
+++ b/test/components/previews/phlex_ui/avatar_preview.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class AvatarPreview < Lookbook::Preview
+ # Avatar sizes
+ # ---------------
+ # @param size [Symbol] select { choices: [sm, md, lg, xl] }
+ def sizes(size: :md)
+ render(TestView.new) do
+ Avatar(size:) do
+ AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper")
+ end
+ end
+ end
+
+ # Avatar Images
+ def image
+ render(TestView.new) do
+ Avatar do
+ AvatarImage(src: "https://avatars.githubusercontent.com/u/246692?v=4", alt: "joeldrapper")
+ end
+ end
+ end
+
+ # Avatar fallback
+ def fallback
+ render(TestView.new) do
+ Avatar { AvatarFallback { "JD" } }
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/badge_preview.rb b/test/components/previews/phlex_ui/badge_preview.rb
new file mode 100644
index 0000000..afc5531
--- /dev/null
+++ b/test/components/previews/phlex_ui/badge_preview.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# rubocop:disable Layout/LineLength
+
+module PhlexUi
+ class BadgePreview < Lookbook::Preview
+ # Default Badge
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, secondary, outline, destructive, success, warning, slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose] }
+ def default(variant: :primary)
+ render(TestView.new) do
+ Badge(variant:) { "My Badge" }
+ end
+ end
+ end
+end
+# rubocop:enable Layout/LineLength
diff --git a/test/components/previews/phlex_ui/button_preview.rb b/test/components/previews/phlex_ui/button_preview.rb
new file mode 100644
index 0000000..92d9230
--- /dev/null
+++ b/test/components/previews/phlex_ui/button_preview.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class ButtonPreview < Lookbook::Preview
+ # Primary Button
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, destructive, outline, secondary, ghost, link] }
+ # @param size [Symbol] select { choices: [sm, md, lg, xl] }
+ def primary(variant: :primary, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+
+ # Destructive Button
+ def destructive(variant: :destructive, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+
+ # Outline Button
+ def outline(variant: :outline, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+
+ # Secondary Button
+ def secondary(variant: :secondary, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+
+ # Ghost Button
+ def ghost(variant: :ghost, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+
+ # Link Button
+ def link(variant: :link, size: :md)
+ render(TestView.new) do
+ Button(variant:, size:) { variant.capitalize }
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/calendar_preview.rb b/test/components/previews/phlex_ui/calendar_preview.rb
new file mode 100644
index 0000000..a1a7b95
--- /dev/null
+++ b/test/components/previews/phlex_ui/calendar_preview.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CalendarPreview < Lookbook::Preview
+ def default
+ render(TestView.new) do
+ div(class: "space-y-4") do
+ Input(type: "string", placeholder: "Select a date", class: "rounded-md border shadow", id: "date", data_controller: "input")
+ Calendar(input_id: "#date", class: "rounded-md border shadow")
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/card_preview.rb b/test/components/previews/phlex_ui/card_preview.rb
new file mode 100644
index 0000000..8c4e6e4
--- /dev/null
+++ b/test/components/previews/phlex_ui/card_preview.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CardPreview < Lookbook::Preview
+ # Default CardPreview
+ # ---------------
+ def default
+ render(TestView.new) do
+ Card(class: "w-96") do
+ CardHeader do
+ CardTitle { 'You might like "PhlexUi"' }
+ CardDescription { "@joeldrapper" }
+ end
+
+ CardContent do
+ AspectRatio(aspect_ratio: "16/9", class: "rounded-md overflow-hidden border") do
+ img(
+ alt: "Placeholder",
+ loading: "lazy",
+ src: helpers.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
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/checkbox_preview.rb b/test/components/previews/phlex_ui/checkbox_preview.rb
new file mode 100644
index 0000000..259a410
--- /dev/null
+++ b/test/components/previews/phlex_ui/checkbox_preview.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CheckboxPreview < Lookbook::Preview
+ # Default Checkbox
+ # ---------------
+ def default
+ render(TestView.new) do
+ Checkbox(id: "terms")
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/clipboard_preview.rb b/test/components/previews/phlex_ui/clipboard_preview.rb
new file mode 100644
index 0000000..49ebe5a
--- /dev/null
+++ b/test/components/previews/phlex_ui/clipboard_preview.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class ClipboardPreview < Lookbook::Preview
+ # Default Clipboard
+ # ---------------
+ def default
+ render(TestView.new) do
+ Clipboard(success: "Copiado!", error: "Falha ao copiar!", class: "relative") do
+ ClipboardSource(class: "hidden") { span { "Nascer herdeiro!!!" } }
+
+ ClipboardTrigger do
+ Link(href: "#", class: "gap-1") do
+ TypographyP(size: :small, class: "text-primary") { "Copiar segredo do sucesso!!!" }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/codeblock_preview.rb b/test/components/previews/phlex_ui/codeblock_preview.rb
new file mode 100644
index 0000000..46ba92e
--- /dev/null
+++ b/test/components/previews/phlex_ui/codeblock_preview.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CodeblockPreview < Lookbook::Preview
+ # Default Codeblock
+ # ---------------
+ def default
+ code = <<~CODE
+ def hello_world
+ puts "Hello, world!"
+ end
+ CODE
+
+ render(TestView.new) do
+ Codeblock(code, syntax: :ruby)
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/collapsible_preview.rb b/test/components/previews/phlex_ui/collapsible_preview.rb
new file mode 100644
index 0000000..aee20a0
--- /dev/null
+++ b/test/components/previews/phlex_ui/collapsible_preview.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CollapsiblePreview < Lookbook::Preview
+ # Default Collapsible
+ # ---------------
+ def default
+ render(TestView.new) do
+ 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
+ plain(helpers.lucide_icon("chevrons-up-down", class: "w-4 h-4"))
+ 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
+ "PhlexUI/phlex_ui"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/command_preview.rb b/test/components/previews/phlex_ui/command_preview.rb
new file mode 100644
index 0000000..590bdc4
--- /dev/null
+++ b/test/components/previews/phlex_ui/command_preview.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class CommandPreview < Lookbook::Preview
+ # Default Command
+ # ---------------
+ def default
+ render(TestView.new) do
+ 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
+ plain(helpers.lucide_icon("search", class: "w-4 h-4 mr-1.5"))
+ span(class: "text-muted-foreground font-normal") { plain("Search") }
+ end
+ end
+ end
+
+ CommandDialogContent do
+ Command do
+ CommandInput(placeholder: "Type a command or search..")
+ CommandEmpty { "No results found" }
+ CommandList do
+ CommandGroup(title: "Components") do
+ [
+ {name: "Accordion", path: "#"},
+ {name: "Alert", path: "#"},
+ {name: "Alert Dialog", path: "#"},
+ {name: "Aspect Ratio", path: "#"},
+ {name: "Avatar", path: "#"},
+ {name: "Badge", path: "#"}
+ ].each do |component|
+ CommandItem(value: component[:name], href: component[:path]) do
+ plain(helpers.lucide_icon("circle-arrow-right", class: "w-4 h-4"))
+ plain(component[:name])
+ end
+ end
+ end
+
+ CommandGroup(title: "Settings") do
+ [
+ {name: "Profile", path: "#"},
+ {name: "Mail", path: "#"},
+ {name: "Settings", path: "#"}
+ ].each do |setting|
+ CommandItem(value: setting[:name], href: setting[:path]) do
+ plain(helpers.lucide_icon("circle-arrow-right", class: "w-4 h-4"))
+ plain(setting[:name])
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/context_menu_preview.rb b/test/components/previews/phlex_ui/context_menu_preview.rb
new file mode 100644
index 0000000..9b3d9a7
--- /dev/null
+++ b/test/components/previews/phlex_ui/context_menu_preview.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class ContextMenuPreview < Lookbook::Preview
+ # Default ContextMenu
+ # ---------------
+ def default
+ render(TestView.new) do
+ 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
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/date_picker_preview.rb b/test/components/previews/phlex_ui/date_picker_preview.rb
new file mode 100644
index 0000000..9d84c09
--- /dev/null
+++ b/test/components/previews/phlex_ui/date_picker_preview.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class DatePickerPreview < Lookbook::Preview
+ # Default DatePicker
+ # ---------------
+ def default
+ render(TestView.new) do
+ div(class: "space-y-4 w-[260px]") do
+ Popover(options: {trigger: "focusin"}) do
+ PopoverTrigger(class: "w-full") do
+ div(class: "grid w-full max-w-sm items-center gap-1.5") do
+ Label(for: "date") { "Select a date" }
+ Input(type: "string", placeholder: "Select a date", class: "rounded-md border shadow", id: "date", data_controller: "input")
+ end
+ end
+ PopoverContent do
+ Calendar(input_id: "#date")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/dialog_preview.rb b/test/components/previews/phlex_ui/dialog_preview.rb
new file mode 100644
index 0000000..8cd68ce
--- /dev/null
+++ b/test/components/previews/phlex_ui/dialog_preview.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class DialogPreview < Lookbook::Preview
+ # Default Dialog
+ # ---------------
+ # @param size [Symbol] select { choices: [sm, md, lg, xl, full] }
+ def default(size: :md)
+ render(TestView.new) do
+ Dialog do
+ DialogTrigger { Button { "Open Dialog" } }
+ DialogContent(size:) do
+ DialogHeader do
+ DialogTitle { "PhlexUI to the rescue" }
+ DialogDescription do
+ "PhlexUI helps you build accessible standard compliant web apps with ease"
+ end
+ end
+ DialogMiddle do
+ AspectRatio(aspect_ratio: "16/9", class: "rounded-md overflow-hidden border") do
+ img(
+ alt: "Placeholder",
+ loading: "lazy",
+ src: helpers.image_path("pattern.jpg")
+ )
+ end
+ end
+ DialogFooter do
+ Button(variant: :outline, data: {action: "click->dismissable#dismiss"}) { "Cancel" }
+
+ Button { "Save" }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/dropdown_menu_preview.rb b/test/components/previews/phlex_ui/dropdown_menu_preview.rb
new file mode 100644
index 0000000..0541b42
--- /dev/null
+++ b/test/components/previews/phlex_ui/dropdown_menu_preview.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# rubocop:disable Layout/LineLength
+
+module PhlexUi
+ class DropdownMenuPreview < Lookbook::Preview
+ # Default DropdownMenu
+ # ---------------
+ # @param placement [String] select { choices: ['top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end', 'bottom', 'bottom-start', 'bottom-end'] }
+ def default(placement: "top")
+ render(TestView.new) do
+ DropdownMenu(options: {placement:}) 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
+ end
+ end
+ end
+end
+# rubocop:enable Layout/LineLength
diff --git a/test/components/previews/phlex_ui/hover_card_preview.rb b/test/components/previews/phlex_ui/hover_card_preview.rb
new file mode 100644
index 0000000..175662f
--- /dev/null
+++ b/test/components/previews/phlex_ui/hover_card_preview.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class HoverCardPreview < Lookbook::Preview
+ # Default HoverCard
+ # ---------------
+ def default
+ render(TestView.new) do
+ HoverCard do
+ HoverCardTrigger { Button(variant: :link) { "@joeldrapper" } }
+
+ 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") { "Creator of Phlex Components. Ruby on Rails developer." }
+ div(class: "flex items-center pt-2") do
+ plain(helpers.lucide_icon("calendar-days", class: "mr-2 h-4 w-4 opacity-70"))
+ span(class: "text-xs text-muted-foreground") { "Joined December 2021" }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/input_preview.rb b/test/components/previews/phlex_ui/input_preview.rb
new file mode 100644
index 0000000..4f91c7d
--- /dev/null
+++ b/test/components/previews/phlex_ui/input_preview.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class InputPreview < Lookbook::Preview
+ # Email Input
+ # ---------------
+ def email
+ render(TestView.new) do
+ Input(type: "email", placeholder: "Email", class: "max-w-sm")
+ end
+ end
+
+ # File input
+ def file
+ render(TestView.new) do
+ FormItem do
+ Label(for: "picture") { "Picture" }
+ Input(type: "file", id: "picture")
+ end
+ end
+ end
+
+ # Disabled input
+ def disabled
+ render(TestView.new) do
+ Input(disabled: true, type: "email", placeholder: "Email", class: "max-w-sm")
+ end
+ end
+
+ # with label
+ def with_label
+ render(TestView.new) do
+ FormItem do
+ Label(for: "email1") { "Email" }
+ Input(type: "email", placeholder: "Email", id: "email1")
+ end
+ end
+ end
+
+ # with error
+ def with_error
+ render(TestView.new) do
+ FormItem do
+ Label(for: "email1") { "Email" }
+ Input(
+ type: "email",
+ placeholder: "Email",
+ id: "email1",
+ value: "joel@mail",
+ error: "Invalid email address"
+ )
+ end
+ end
+ end
+
+ # with button
+ def with_button
+ render(TestView.new) do
+ Form(class: "w-full max-w-sm") do
+ FormSpacer do
+ FormItem do
+ Label(for: "username") { "Username" }
+ Input(type: "string", placeholder: "Username", id: "username")
+ Hint { "Can only contain letters, numbers, and underscores." }
+ end
+
+ Button(type: "submit") { "Submit" }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/link_preview.rb b/test/components/previews/phlex_ui/link_preview.rb
new file mode 100644
index 0000000..059eb64
--- /dev/null
+++ b/test/components/previews/phlex_ui/link_preview.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class LinkPreview < Lookbook::Preview
+ # Default Link
+ # ---------------
+ def default
+ render(TestView.new) do
+ Link(href: "#") { "Link" }
+ end
+ end
+
+ # Primary Link
+ # ---------------
+ def primary
+ render(TestView.new) do
+ Link(href: "#", variant: :primary) { "Primary" }
+ end
+ end
+
+ # Secondary Link
+ # ---------------
+ def secondary
+ render(TestView.new) do
+ Link(href: "#", variant: :secondary) { "Secondary" }
+ end
+ end
+
+ # Destructive Link
+ # ---------------
+ def destructive
+ render(TestView.new) do
+ Link(href: "#", variant: :destructive) { "Destructive" }
+ end
+ end
+
+ # ghost Link
+ # ---------------
+ def ghost
+ render(TestView.new) do
+ Link(href: "#", variant: :ghost) { "Ghost" }
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/pagination_preview.rb b/test/components/previews/phlex_ui/pagination_preview.rb
new file mode 100644
index 0000000..9d4b8a1
--- /dev/null
+++ b/test/components/previews/phlex_ui/pagination_preview.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class PaginationPreview < Lookbook::Preview
+ # Default Pagination
+ # ---------------
+ def default
+ render(TestView.new) do
+ Pagination do
+ PaginationContent do
+ PaginationItem(href: "#") do
+ plain(helpers.lucide_icon("chevrons-left", class: "w-4 h-4"))
+ plain "First"
+ end
+
+ PaginationItem(href: "#") do
+ plain(helpers.lucide_icon("chevron-left", class: "w-4 h-4"))
+ plain "Prev"
+ end
+
+ PaginationEllipsis()
+
+ PaginationItem(href: "#") { "4" }
+ PaginationItem(href: "#", active: true) { "5" }
+ PaginationItem(href: "#") { "6" }
+
+ PaginationEllipsis()
+
+ PaginationItem(href: "#") do
+ plain "Next"
+ plain(helpers.lucide_icon("chevron-right", class: "w-4 h-4"))
+ end
+
+ PaginationItem(href: "#") do
+ plain "Last"
+ plain(helpers.lucide_icon("chevrons-right", class: "w-4 h-4"))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/popover_preview.rb b/test/components/previews/phlex_ui/popover_preview.rb
new file mode 100644
index 0000000..a914815
--- /dev/null
+++ b/test/components/previews/phlex_ui/popover_preview.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+# rubocop:disable Layout/LineLength
+
+module PhlexUi
+ class PopoverPreview < Lookbook::Preview
+ # Default Popover
+ # ---------------
+ # @param placement [String] select { choices: ['top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end', 'bottom', 'bottom-start', 'bottom-end'] }
+ def default(placement: "top")
+ render(TestView.new) do
+ Popover(options: {placement:}) do
+ PopoverTrigger(class: "w-full") do
+ Button(variant: :outline) { "Open Popover" }
+ end
+
+ PopoverContent(class: "w-40") do
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ plain(helpers.lucide_icon("circle-user", class: "w-4 h-4 mr-2"))
+ plain("Profile")
+ end
+
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ plain(helpers.lucide_icon("cog", class: "w-4 h-4 mr-2"))
+ plain("Settings")
+ end
+
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ plain(helpers.lucide_icon("log-out", class: "w-4 h-4 mr-2"))
+ plain("Logout")
+ end
+ end
+ end
+ end
+ end
+
+ # Trigger Popover
+ # ---------------
+ def trigger
+ render(TestView.new) do
+ Popover(options: {trigger: "click"}) do
+ PopoverTrigger(class: "w-full") do
+ Button(variant: :outline) { "Open Popover" }
+ end
+
+ PopoverContent(class: "w-40") do
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ plain(helpers.lucide_icon("circle-user", class: "w-4 h-4 mr-2"))
+ plain("Profile")
+ end
+
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ plain(helpers.lucide_icon("cog", class: "w-4 h-4 mr-2"))
+ plain("Settings")
+ end
+
+ Link(href: "#", variant: :ghost, class: "block w-full justify-start pl-2") do
+ link.plain(helpers.lucide_icon("log-out", class: "w-4 h-4 mr-2"))
+ link.plain("Logout")
+ end
+ end
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Layout/LineLength
diff --git a/test/components/previews/phlex_ui/select_preview.rb b/test/components/previews/phlex_ui/select_preview.rb
new file mode 100644
index 0000000..831ac25
--- /dev/null
+++ b/test/components/previews/phlex_ui/select_preview.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class SelectPreview < Lookbook::Preview
+ def default
+ render(TestView.new) do
+ Select(class: "w-56 flex items-center justify-center") 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
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/sheet_preview.rb b/test/components/previews/phlex_ui/sheet_preview.rb
new file mode 100644
index 0000000..f81ba2d
--- /dev/null
+++ b/test/components/previews/phlex_ui/sheet_preview.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class SheetPreview < Lookbook::Preview
+ # Default Sheet
+ # ---------------
+ # @param side [Symbol] select { choices: [top, right, bottom, left] }
+ # @param size [String] select { choices: ["", "w-3/4", "w-1/2"] }
+ def default(side: :top, size: "")
+ render(TestView.new) do
+ Sheet do
+ SheetTrigger { Button(variant: :outline) { "Open Sheet" } }
+ SheetContent(side:, class: size) do
+ SheetHeader do
+ SheetTitle { "Edit profile" }
+ SheetDescription { "Make changes to your profile here. Click save when you're done." }
+ end
+
+ Form do
+ SheetMiddle do
+ FormSpacer do
+ FormItem do
+ Label { "Name" }
+ Input(placeholder: "Joel Drapper") { "Joel Drapper" }
+ end
+
+ FormItem do
+ Label { "Email" }
+ Input(placeholder: "joel@drapper.me")
+ end
+ end
+ end
+
+ SheetFooter do
+ Button(variant: :outline, data: {action: "click->dismissable#dismiss"}) { "Cancel" }
+ Button(type: "submit") { "Save" }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/shortcut_key_preview.rb b/test/components/previews/phlex_ui/shortcut_key_preview.rb
new file mode 100644
index 0000000..12e596e
--- /dev/null
+++ b/test/components/previews/phlex_ui/shortcut_key_preview.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class ShortcutKeyPreview < Lookbook::Preview
+ def default
+ render(TestView.new) do
+ 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
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/table_preview.rb b/test/components/previews/phlex_ui/table_preview.rb
new file mode 100644
index 0000000..b33720e
--- /dev/null
+++ b/test/components/previews/phlex_ui/table_preview.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class TablePreview < Lookbook::Preview
+ # Default TablePreview
+ # ---------------
+ def default
+ render(TestView.new) do
+ 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
+ [
+ {identifier: "INV-0001", status: "Active", method: "Credit Card", amount: 100},
+ {identifier: "INV-0002", status: "Active", method: "Bank Transfer", amount: 230},
+ {identifier: "INV-0003", status: "Pending", method: "PayPal", amount: 350},
+ {identifier: "INV-0004", status: "Inactive", method: "Credit Card", amount: 10}
+ ].each do |invoice|
+ TableRow do
+ TableCell(class: "font-medium") { invoice[:identifier] }
+ TableCell { invoice[:status] }
+ TableCell { invoice[:method] }
+ TableCell(class: "text-right") { invoice[:amount] }
+ end
+ end
+ end
+
+ TableFooter do
+ TableRow do
+ TableHead(class: "font-medium", colspan: 3) { "Total" }
+ TableHead(class: "font-medium text-right") { "780" }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/tabs_preview.rb b/test/components/previews/phlex_ui/tabs_preview.rb
new file mode 100644
index 0000000..b9f0967
--- /dev/null
+++ b/test/components/previews/phlex_ui/tabs_preview.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class TabsPreview < Lookbook::Preview
+ # Default TabsPreview
+ # ---------------
+ def default
+ render(TestView.new) do
+ 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
+ plain("Account")
+ plain("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
+ plain("Password")
+ plain("Change your password here. After saving, you'll be logged out.")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/theme_toggle_preview.rb b/test/components/previews/phlex_ui/theme_toggle_preview.rb
new file mode 100644
index 0000000..9587dc9
--- /dev/null
+++ b/test/components/previews/phlex_ui/theme_toggle_preview.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class ThemeTogglePreview < Lookbook::Preview
+ def default
+ render(TestView.new) do
+ ThemeToggle do |toggle|
+ toggle.light_mode do
+ Button(variant: :ghost, icon: true) do
+ plain(helpers.lucide_icon("sun", class: "w-4 h-4"))
+ end
+ end
+
+ toggle.dark_mode do
+ Button(variant: :ghost, icon: true) do
+ plain(helpers.lucide_icon("moon", class: "w-4 h-4"))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/tooltip_preview.rb b/test/components/previews/phlex_ui/tooltip_preview.rb
new file mode 100644
index 0000000..3431069
--- /dev/null
+++ b/test/components/previews/phlex_ui/tooltip_preview.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class TooltipPreview < Lookbook::Preview
+ # Default TooltipPreview
+ # ---------------
+ def default
+ render(TestView.new) do
+ Tooltip do
+ TooltipTrigger do
+ Button(variant: :outline, icon: true) do
+ plain(ActionController::Base.helpers.lucide_icon("bookmark", class: "w-4 h-4"))
+ end
+ end
+
+ TooltipContent { plain("Add to library") }
+ end
+ end
+ end
+ end
+end
diff --git a/test/components/previews/phlex_ui/typography_preview.rb b/test/components/previews/phlex_ui/typography_preview.rb
new file mode 100644
index 0000000..a5b02cd
--- /dev/null
+++ b/test/components/previews/phlex_ui/typography_preview.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module PhlexUi
+ class TypographyPreview < Lookbook::Preview
+ # H1
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, title] }
+ # @param weight [Symbol] select { choices: [medium, profile] }
+ def h1(variant: :primary, weight: :medium)
+ render(TestView.new) do
+ TypographyH1(variant:, weight:) { "This is an H1 heading" }
+ end
+ end
+
+ # H2
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, title] }
+ # @param weight [Symbol] select { choices: [medium, profile] }
+ def h2(variant: :primary, weight: :medium)
+ render(TestView.new) do
+ TypographyH2(variant:, weight:) { "This is an H2 heading" }
+ end
+ end
+
+ # H3
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, title] }
+ # @param weight [Symbol] select { choices: [medium, profile] }
+ def h3(variant: :primary, weight: :medium)
+ render(TestView.new) do
+ TypographyH3(variant:, weight:) { "This is an H3 heading" }
+ end
+ end
+
+ # H4
+ # ---------------
+ # @param variant [Symbol] select { choices: [primary, title] }
+ # @param weight [Symbol] select { choices: [medium, profile] }
+ def h4(variant: :primary, weight: :medium)
+ render(TestView.new) do
+ TypographyH4(variant:, weight:) { "This is an H4 heading" }
+ end
+ end
+
+ # p
+ # ---------------
+ # @param size [Symbol] select { choices: [xsmall, small, base] }
+ # @param weight [Symbol] select { choices: [medium, normal] }
+ # @param color [Symbol] select { choices: [primary, title, secondary, disabled] }
+ def p(size: :base, weight: :medium, color: :primary)
+ render(TestView.new) do
+ TypographyP(size:, weight:, color:) { "This is an Paragraph" }
+ end
+ end
+ end
+end