Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

* Breadcrumb component, including Flowbite::BreadcrumbItem with Flowbite::BreadcrumbItem::First and Flowbite::BreadcrumbItem::Current variants.
* DateTime input field components.
* Toast component.

### Changed

### Removed
Expand Down
33 changes: 33 additions & 0 deletions app/components/flowbite/breadcrumb.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Flowbite
# Renders a breadcrumb navigation component.
#
# See https://flowbite.com/docs/components/breadcrumb/
#
# @example Basic usage with BreadcrumbItem components
# <%= render Flowbite::Breadcrumb.new do |breadcrumb| %>
# <% breadcrumb.with_item do %>
# <%= render Flowbite::BreadcrumbItem::First.new(href: "/") { "Home" } %>
# <% end %>
# <% breadcrumb.with_item do %>
# <%= render Flowbite::BreadcrumbItem.new(href: "/projects") { "Projects" } %>
# <% end %>
# <% breadcrumb.with_item do %>
# <%= render Flowbite::BreadcrumbItem::Current.new { "Current Page" } %>
# <% end %>
# <% end %>
class Breadcrumb < ViewComponent::Base
renders_many :items

def call
content_tag(:nav, class: "flex", "aria-label": "Breadcrumb") do
content_tag(:ol, class: "inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse") do
items.each do |item|
concat(item)
end
end
end
end
end
end
26 changes: 26 additions & 0 deletions app/components/flowbite/breadcrumb_home.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Flowbite
# Renders a breadcrumb home icon.
#
# This is typically used as a prefix icon in the first breadcrumb item,
# but can be used standalone if needed.
#
# @example Standalone usage
# <%= render Flowbite::BreadcrumbHome.new %>
class BreadcrumbHome < ViewComponent::Base
def call
tag.svg(
class: "w-3 h-3 me-2.5",
"aria-hidden": "true",
xmlns: "http://www.w3.org/2000/svg",
fill: "currentColor",
viewBox: "0 0 20 20"
) do
tag.path(
d: "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
)
end
end
end
end
48 changes: 48 additions & 0 deletions app/components/flowbite/breadcrumb_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

module Flowbite
# Base class for rendering a breadcrumb item (middle items in the breadcrumb trail).
#
# @param href [String] The URL for the breadcrumb link.
# @param options [Hash] Additional HTML attributes to pass to the link element.
#
# @example Middle item
# <%= render Flowbite::BreadcrumbItem.new(href: "/projects") { "Projects" } %>
class BreadcrumbItem < ViewComponent::Base
attr_reader :href, :options

def initialize(href:, **options)
super()
@href = href
@options = options
end

def call
content_tag(:li, item_options) do
content_tag(:div, class: "flex items-center") do
concat(render(prefix_icon)) if prefix_icon
concat(render_link)
end
end
end

protected

def item_options
{}
end

def prefix_icon
Flowbite::BreadcrumbSeparator.new
end

def render_link
link_options = {class: link_classes}.merge(options)
content_tag(:a, content, href: href, **link_options)
end

def link_classes
["ms-1", "text-sm", "font-medium", "text-body", "hover:text-fg-brand"]
end
end
end
33 changes: 33 additions & 0 deletions app/components/flowbite/breadcrumb_item/current.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Flowbite
class BreadcrumbItem
# Renders the current page breadcrumb item.
# Current items are rendered as non-interactive spans with different styling.
#
# @param options [Hash] Additional HTML attributes to pass to the span element.
#
# @example Current page item
# <%= render Flowbite::BreadcrumbItem::Current.new { "Current Page" } %>
class Current < BreadcrumbItem
def initialize(**options)
super(href: nil, **options)
end

protected

def item_options
{"aria-current": "page"}
end

def render_link
link_options = {class: link_classes}.merge(options)
content_tag(:span, content, **link_options)
end

def link_classes
["ms-1", "text-sm", "font-medium", "text-body-subtle"]
end
end
end
end
35 changes: 35 additions & 0 deletions app/components/flowbite/breadcrumb_item/first.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Flowbite
class BreadcrumbItem
# Renders the first breadcrumb item (typically home).
# First items don't show a separator icon.
#
# @param href [String] The URL for the breadcrumb link.
# @param options [Hash] Additional HTML attributes to pass to the link element.
#
# @example First item
# <%= render Flowbite::BreadcrumbItem::First.new(href: "/") { "Home" } %>
class First < BreadcrumbItem
protected

def item_options
{class: "inline-flex items-center"}
end

def link_classes
["text-sm", "font-medium", "inline-flex", "items-center", "text-body", "hover:text-fg-brand"]
end

def prefix_icon
nil
end

def render_link
icon = render(Flowbite::BreadcrumbHome.new)
link_options = {class: link_classes}.merge(options)
content_tag(:a, safe_join([icon, content]), href: href, **link_options)
end
end
end
end
32 changes: 32 additions & 0 deletions app/components/flowbite/breadcrumb_separator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module Flowbite
# Renders a breadcrumb separator icon.
#
# This is automatically used by BreadcrumbItem components, but can be
# used standalone if needed.
#
# @example Standalone usage
# <%= render Flowbite::BreadcrumbSeparator.new %>
class BreadcrumbSeparator < ViewComponent::Base
def call
tag.svg(
class: "w-3.5 h-3.5 rtl:rotate-180 text-body",
"aria-hidden": "true",
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
height: 24,
viewBox: "0 0 24 24",
width: 24
) do
tag.path(
stroke: "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": "2",
d: "m9 5 7 7-7 7"
)
end
end
end
end
11 changes: 11 additions & 0 deletions app/components/flowbite/input/date_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Flowbite
module Input
class DateTime < Field
def input_field_type
:datetime_field
end
end
end
end
13 changes: 13 additions & 0 deletions app/components/flowbite/input_field/date_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Flowbite
class InputField
class DateTime < InputField
protected

def input_component
Flowbite::Input::DateTime
end
end
end
end
34 changes: 34 additions & 0 deletions app/components/flowbite/toast.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Flowbite
# Renders a toast notification element.
#
# See https://flowbite.com/docs/components/toast/
#
# @param class [Array<String>] Additional CSS classes for the toast container.
# @param dismissible [Boolean] Whether the toast can be dismissed (default: true).
# @param message [String] The message to display in the toast.
# @param options [Hash] Additional HTML options for the toast container.
# @param style [Symbol] The color style of the toast (:default, :success, :danger, :warning).
class Toast < ViewComponent::Base
class << self
def classes
["flex", "items-center", "w-full", "max-w-xs", "p-4", "text-body", "bg-neutral-primary-soft", "rounded-base", "shadow-xs", "border", "border-default"]
end
end

attr_reader :dismissible, :message, :options, :style

def initialize(message:, dismissible: true, style: :default, class: nil, **options)
@message = message
@style = style
@dismissible = dismissible
@class = Array.wrap(binding.local_variable_get(:class))
@options = options
end

def container_classes
self.class.classes + @class
end
end
end
5 changes: 5 additions & 0 deletions app/components/flowbite/toast/icon.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="<%= container_classes.join(" ") %>">
<svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="<%= svg_path %>"/>
</svg>
</div>
57 changes: 57 additions & 0 deletions app/components/flowbite/toast/icon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Flowbite
class Toast
# Renders an icon for a toast notification.
#
# @param style [Symbol] The color style of the icon (:default, :success, :danger, :warning).
class Icon < ViewComponent::Base
class << self
def classes(style: :default)
styles.fetch(style).fetch(:classes)
end

def svg_path(style: :default)
styles.fetch(style).fetch(:svg_path)
end

# rubocop:disable Layout/LineLength
def styles
{
default: {
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-blue-500", "bg-blue-100", "rounded-lg", "dark:bg-blue-800", "dark:text-blue-200"],
svg_path: "M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"
},
success: {
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-green-500", "bg-green-100", "rounded-lg", "dark:bg-green-800", "dark:text-green-200"],
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"
},
danger: {
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-red-500", "bg-red-100", "rounded-lg", "dark:bg-red-800", "dark:text-red-200"],
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"
},
warning: {
classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-orange-500", "bg-orange-100", "rounded-lg", "dark:bg-orange-700", "dark:text-orange-200"],
svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"
}
}.freeze
end
# rubocop:enable Layout/LineLength
end

attr_reader :style

def initialize(style: :default)
@style = style
end

def container_classes
self.class.classes(style: style)
end

def svg_path
self.class.svg_path(style: style)
end
end
end
end
40 changes: 40 additions & 0 deletions app/components/flowbite/toast/toast.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div
class="<%= container_classes.join(" ") %>"
role="alert"
<%= options.map { |k, v| "#{k}=\"#{v}\"" }.join(" ").html_safe %>
>
<%= render Flowbite::Toast::Icon.new(style: style) %>

<div class="ms-3 text-sm font-normal"><%= message %></div>

<% if dismissible %>
<%# Styles from https://flowbite.com/docs/components/toast/#default-toast %>
<button
type="button"
class="
ms-auto flex items-center justify-center text-body
hover:text-heading bg-transparent box-border border
border-transparent hover:bg-neutral-secondary-medium focus:ring-4
focus:ring-neutral-tertiary font-medium leading-5 rounded text-sm
h-8 w-8 focus:outline-none
"
aria-label="Close"
>
<svg
class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
/>
</svg>
</button>
<% end %>
</div>
2 changes: 1 addition & 1 deletion demo/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ../..
specs:
flowbite-components (0.1.3)
flowbite-components (0.1.4)
view_component (>= 4.0.0)

GEM
Expand Down
Loading