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