flowbite-components 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +3 -5
- data/app/components/flowbite/alert/alert.html.erb +36 -0
- data/app/components/flowbite/alert/icon.rb +53 -0
- data/app/components/flowbite/alert.rb +156 -0
- data/app/components/flowbite/avatar/avatar.html.erb +23 -0
- data/app/components/flowbite/avatar/dot.rb +85 -0
- data/app/components/flowbite/avatar/placeholder_icon.html.erb +13 -0
- data/app/components/flowbite/avatar/placeholder_icon.rb +44 -0
- data/app/components/flowbite/avatar.rb +132 -0
- data/app/components/flowbite/badge/badge.html.erb +7 -0
- data/app/components/flowbite/badge/dot.rb +45 -0
- data/app/components/flowbite/badge/pill.rb +40 -0
- data/app/components/flowbite/badge.rb +116 -0
- data/app/components/flowbite/card.rb +4 -8
- data/app/components/flowbite/input_field/checkbox.rb +7 -7
- data/app/components/flowbite/input_field/input_field.html.erb +1 -0
- data/app/components/flowbite/input_field/select.rb +1 -1
- data/app/components/flowbite/input_field.rb +29 -22
- data/app/components/flowbite/pagination/chevron_left_icon.rb +34 -0
- data/app/components/flowbite/pagination/chevron_right_icon.rb +34 -0
- data/app/components/flowbite/pagination/gap.rb +24 -0
- data/app/components/flowbite/pagination/link.rb +52 -0
- data/app/components/flowbite/pagination/next_link.rb +67 -0
- data/app/components/flowbite/pagination/pagination.html.erb +11 -0
- data/app/components/flowbite/pagination/previous_link.rb +67 -0
- data/app/components/flowbite/pagination.rb +63 -0
- data/app/components/flowbite/progress/progress.html.erb +14 -0
- data/app/components/flowbite/progress.rb +163 -0
- data/app/components/flowbite/rating/rating.html.erb +13 -0
- data/app/components/flowbite/rating/star.html.erb +13 -0
- data/app/components/flowbite/rating/star.rb +59 -0
- data/app/components/flowbite/rating.rb +64 -0
- data/app/components/flowbite/sidebar/item.rb +68 -0
- data/app/components/flowbite/sidebar/navigation.rb +62 -0
- data/app/components/flowbite/sidebar.rb +62 -0
- data/app/components/flowbite/spinner/spinner.html.erb +20 -0
- data/app/components/flowbite/spinner.rb +66 -0
- data/app/components/flowbite/toast/close_button.html.erb +17 -0
- data/app/components/flowbite/toast/close_button.rb +32 -0
- data/app/components/flowbite/toast/icon.html.erb +8 -2
- data/app/components/flowbite/toast/toast.html.erb +7 -34
- data/app/components/flowbite/toast.rb +39 -1
- data/lib/flowbite/components/version.rb +1 -1
- metadata +39 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<% if label_outside? %>
|
|
2
|
+
<div class="flex justify-between mb-1">
|
|
3
|
+
<span class="text-sm font-medium text-body"><%= label %></span>
|
|
4
|
+
<span class="text-sm font-medium text-body"><%= percentage %></span>
|
|
5
|
+
</div>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<%= tag.div(class: classes.join(" "), **track_options) do %>
|
|
9
|
+
<div class="<%= bar_classes.join(" ") %>" style="<%= width_style %>">
|
|
10
|
+
<% if label_inside? %>
|
|
11
|
+
<%= percentage %>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# Renders a progress bar indicating the completion of a task.
|
|
5
|
+
#
|
|
6
|
+
# The progress value is clamped to the range 0 to 100 and used as the
|
|
7
|
+
# width of the bar.
|
|
8
|
+
#
|
|
9
|
+
# The track is rendered with role="progressbar" and the matching ARIA
|
|
10
|
+
# value attributes. The +label+, if given, is used as its accessible name.
|
|
11
|
+
# Pass +aria: {label: "..."}+ to name a progress bar without a visible
|
|
12
|
+
# label.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# <%= render(Flowbite::Progress.new(progress: 45)) %>
|
|
16
|
+
#
|
|
17
|
+
# @example With a custom style and size
|
|
18
|
+
# <%= render(Flowbite::Progress.new(progress: 45, size: :lg, style: :success)) %>
|
|
19
|
+
#
|
|
20
|
+
# @example With the label inside the bar
|
|
21
|
+
# <%= render(Flowbite::Progress.new(label_position: :inside, progress: 45)) %>
|
|
22
|
+
#
|
|
23
|
+
# @example With a label outside the bar
|
|
24
|
+
# <%= render(Flowbite::Progress.new(label: "Flowbite", label_position: :outside, progress: 45)) %>
|
|
25
|
+
#
|
|
26
|
+
# @example With an accessible name and no visible label
|
|
27
|
+
# <%= render(Flowbite::Progress.new(aria: {label: "Upload progress"}, progress: 45)) %>
|
|
28
|
+
#
|
|
29
|
+
# @see https://flowbite.com/docs/components/progress/
|
|
30
|
+
# @lookbook_embed ProgressPreview
|
|
31
|
+
class Progress < ViewComponent::Base
|
|
32
|
+
LABEL_POSITIONS = [:inside, :none, :outside].freeze
|
|
33
|
+
|
|
34
|
+
SIZES = {
|
|
35
|
+
sm: ["h-1.5"],
|
|
36
|
+
default: ["h-2"],
|
|
37
|
+
lg: ["h-2.5"]
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
# Only vertical padding, so the bar can shrink to zero width at 0%.
|
|
41
|
+
INSIDE_LABEL_CLASSES = [
|
|
42
|
+
"text-xs", "font-medium", "text-white", "text-center", "py-0.5",
|
|
43
|
+
"leading-none", "rounded-full", "h-4", "flex", "items-center",
|
|
44
|
+
"justify-center"
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
# @return [Array<String>] The CSS classes for the bar indicating the
|
|
49
|
+
# progress.
|
|
50
|
+
def bar_classes(label_position: :none, size: :default, style: :brand)
|
|
51
|
+
style = styles.fetch(style)
|
|
52
|
+
if label_position == :inside
|
|
53
|
+
style.fetch(:default) + INSIDE_LABEL_CLASSES
|
|
54
|
+
else
|
|
55
|
+
style.fetch(:default) + sizes.fetch(size) + ["rounded-full"]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Array<String>] The CSS classes for the track containing the
|
|
60
|
+
# bar.
|
|
61
|
+
def classes(label_position: :none, size: :default)
|
|
62
|
+
result = ["w-full", "bg-neutral-quaternary", "rounded-full"]
|
|
63
|
+
result += sizes.fetch(size) unless label_position == :inside
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def sizes
|
|
68
|
+
SIZES
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def styles
|
|
72
|
+
Flowbite::Styles.from_hash(
|
|
73
|
+
{
|
|
74
|
+
brand: {default: ["bg-brand"]},
|
|
75
|
+
danger: {default: ["bg-danger"]},
|
|
76
|
+
dark: {default: ["bg-dark"]},
|
|
77
|
+
success: {default: ["bg-success"]},
|
|
78
|
+
warning: {default: ["bg-warning"]}
|
|
79
|
+
}.freeze
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
attr_reader :label, :label_position, :options
|
|
85
|
+
|
|
86
|
+
# @param class [String, Array<String>] Additional CSS classes for the
|
|
87
|
+
# track element.
|
|
88
|
+
# @param label [String] Text to show next to the percentage when
|
|
89
|
+
# label_position is :outside. Also used as the accessible name of the
|
|
90
|
+
# progress bar.
|
|
91
|
+
# @param label_position [Symbol] Where to show the percentage (:inside,
|
|
92
|
+
# :none, :outside).
|
|
93
|
+
# @param options [Hash] Additional HTML options for the track element.
|
|
94
|
+
# @param progress [Numeric] The progress in percent. Values outside the
|
|
95
|
+
# range 0 to 100 are clamped.
|
|
96
|
+
# @param size [Symbol] The height of the progress bar (:sm, :default,
|
|
97
|
+
# :lg). Ignored when label_position is :inside.
|
|
98
|
+
# @param style [Symbol] The color style of the bar (:brand, :danger,
|
|
99
|
+
# :dark, :success, :warning).
|
|
100
|
+
def initialize(progress:, class: nil, label: nil, label_position: :none, size: :default, style: :brand, **options)
|
|
101
|
+
unless LABEL_POSITIONS.include?(label_position)
|
|
102
|
+
raise ArgumentError,
|
|
103
|
+
"Unknown label_position: #{label_position}. " \
|
|
104
|
+
"Available label positions: #{LABEL_POSITIONS.join(", ")}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
108
|
+
@label = label
|
|
109
|
+
@label_position = label_position
|
|
110
|
+
@options = options
|
|
111
|
+
@progress = progress
|
|
112
|
+
@size = size
|
|
113
|
+
@style = style
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [Array<String>] The CSS classes for the bar.
|
|
117
|
+
def bar_classes
|
|
118
|
+
self.class.bar_classes(label_position: label_position, size: @size, style: @style)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Array<String>] The CSS classes for the track.
|
|
122
|
+
def classes
|
|
123
|
+
self.class.classes(label_position: label_position, size: @size) + @class
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def label_inside?
|
|
127
|
+
label_position == :inside
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def label_outside?
|
|
131
|
+
label_position == :outside
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @return [String] The progress formatted as a percentage, ie "45%".
|
|
135
|
+
def percentage
|
|
136
|
+
"#{rounded_progress}%"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [Numeric] The progress clamped to the range 0 to 100.
|
|
140
|
+
def progress
|
|
141
|
+
@progress.clamp(0, 100)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @return [Numeric] The clamped progress rounded to one decimal place.
|
|
145
|
+
# Whole numbers are returned as Integers, ie 45 rather than 45.0.
|
|
146
|
+
def rounded_progress
|
|
147
|
+
value = progress.to_f.round(1)
|
|
148
|
+
(value == value.to_i) ? value.to_i : value
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @return [Hash] The HTML attributes for the track element, including
|
|
152
|
+
# the progressbar role and ARIA values. Merged with the given options.
|
|
153
|
+
def track_options
|
|
154
|
+
aria = {label: label.presence, valuemax: 100, valuemin: 0, valuenow: rounded_progress}.compact
|
|
155
|
+
{aria: aria, role: "progressbar"}.deep_merge(options)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @return [String] The inline style setting the width of the bar.
|
|
159
|
+
def width_style
|
|
160
|
+
"width: #{percentage}"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= tag.div(class: container_classes.join(" "), **options) do %>
|
|
2
|
+
<div class="flex items-center space-x-1">
|
|
3
|
+
<% stars.each do |filled| %>
|
|
4
|
+
<%= render(Flowbite::Rating::Star.new(filled: filled, size: size)) %>
|
|
5
|
+
<% end %>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<span class="sr-only"><%= screen_reader_text %></span>
|
|
9
|
+
|
|
10
|
+
<% if text.present? %>
|
|
11
|
+
<p class="ms-2 text-sm font-medium text-body"><%= text %></p>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
aria-hidden="true"
|
|
3
|
+
class="<%= classes.join(" ") %>"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
height="24"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
width="24"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
>
|
|
10
|
+
<path
|
|
11
|
+
d="M13.849 4.22c-.684-1.626-3.014-1.626-3.698 0L8.397 8.387l-4.552.361c-1.775.14-2.495 2.331-1.142 3.477l3.468 2.937-1.06 4.392c-.413 1.713 1.472 3.067 2.992 2.149L12 19.35l3.897 2.354c1.52.918 3.405-.436 2.992-2.15l-1.06-4.39 3.468-2.938c1.353-1.146.633-3.336-1.142-3.477l-4.552-.36-1.754-4.17Z"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class Rating
|
|
5
|
+
# Renders a single star of a rating.
|
|
6
|
+
#
|
|
7
|
+
# @param filled [Boolean] Whether the star is filled (default: true).
|
|
8
|
+
# @param size [Symbol] The size of the star (:default, :md, :lg).
|
|
9
|
+
#
|
|
10
|
+
# @see https://flowbite.com/docs/components/rating/
|
|
11
|
+
class Star < ViewComponent::Base
|
|
12
|
+
SIZES = {
|
|
13
|
+
default: ["w-5", "h-5"],
|
|
14
|
+
md: ["w-6", "h-6"],
|
|
15
|
+
lg: ["w-7", "h-7"]
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def classes(size: :default, state: :filled, style: :default)
|
|
20
|
+
sizes.fetch(size) + styles.fetch(style).fetch(state)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sizes
|
|
24
|
+
SIZES
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def styles
|
|
28
|
+
Flowbite::Styles.from_hash(
|
|
29
|
+
{
|
|
30
|
+
default: {
|
|
31
|
+
empty: ["text-fg-disabled"],
|
|
32
|
+
filled: ["text-fg-yellow"]
|
|
33
|
+
}
|
|
34
|
+
}.freeze
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :size
|
|
40
|
+
|
|
41
|
+
def initialize(filled: true, size: :default)
|
|
42
|
+
@filled = filled
|
|
43
|
+
@size = size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def classes
|
|
47
|
+
self.class.classes(size: size, state: state)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def filled?
|
|
51
|
+
!!@filled
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def state
|
|
55
|
+
filled? ? :filled : :empty
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# Renders a star rating, showing a value out of a maximum number of stars.
|
|
5
|
+
#
|
|
6
|
+
# The number of filled stars is the value rounded to the nearest whole
|
|
7
|
+
# number (halves round up), clamped between 0 and +max+. The remaining stars
|
|
8
|
+
# are rendered as empty stars. The exact value is announced to screen
|
|
9
|
+
# readers.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# <%= render(Flowbite::Rating.new(value: 4)) %>
|
|
13
|
+
#
|
|
14
|
+
# @example With text and a custom size
|
|
15
|
+
# <%= render(Flowbite::Rating.new(size: :lg, text: "4.95 out of 5", value: 4.95)) %>
|
|
16
|
+
#
|
|
17
|
+
# @see https://flowbite.com/docs/components/rating/
|
|
18
|
+
# @lookbook_embed RatingPreview
|
|
19
|
+
class Rating < ViewComponent::Base
|
|
20
|
+
class << self
|
|
21
|
+
def classes
|
|
22
|
+
["flex", "items-center"]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :max, :options, :size, :text, :value
|
|
27
|
+
|
|
28
|
+
# @param class [String, Array<String>] Additional CSS classes for the
|
|
29
|
+
# container element.
|
|
30
|
+
# @param max [Integer] The total number of stars to render (default: 5).
|
|
31
|
+
# @param options [Hash] Additional HTML options for the container element.
|
|
32
|
+
# @param size [Symbol] The size of the stars (:default, :md, :lg).
|
|
33
|
+
# @param text [String] Optional text rendered after the stars, e.g.
|
|
34
|
+
# "4.95 out of 5".
|
|
35
|
+
# @param value [Numeric] The rating value, e.g. 4 or 4.95.
|
|
36
|
+
def initialize(value:, class: nil, max: 5, size: :default, text: nil, **options)
|
|
37
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
38
|
+
@max = max
|
|
39
|
+
@options = options
|
|
40
|
+
@size = size
|
|
41
|
+
@text = text
|
|
42
|
+
@value = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def container_classes
|
|
46
|
+
self.class.classes + @class
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Integer] The number of stars to render as filled.
|
|
50
|
+
def filled_count
|
|
51
|
+
value.round.clamp(0, max)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [String] The rating as announced to screen readers.
|
|
55
|
+
def screen_reader_text
|
|
56
|
+
"Rated #{value} out of #{max}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Array<Boolean>] One entry per star, true if the star is filled.
|
|
60
|
+
def stars
|
|
61
|
+
Array.new(max) { |index| index < filled_count }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class Sidebar
|
|
5
|
+
# Renders a sidebar navigation item.
|
|
6
|
+
#
|
|
7
|
+
# Each item renders as a list item containing a link. Optionally, an icon
|
|
8
|
+
# can be provided using the +icon+ slot, which will be displayed before the
|
|
9
|
+
# label text.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic item
|
|
12
|
+
# <%= render Flowbite::Sidebar::Item.new(href: "/dashboard") { "Dashboard" } %>
|
|
13
|
+
#
|
|
14
|
+
# @example Item with icon
|
|
15
|
+
# <%= render(Flowbite::Sidebar::Item.new(href: "/dashboard")) do |item| %>
|
|
16
|
+
# <% item.with_icon do %>
|
|
17
|
+
# <svg class="w-5 h-5" ...>...</svg>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# Dashboard
|
|
20
|
+
# <% end %>
|
|
21
|
+
#
|
|
22
|
+
# @viewcomponent_slot icon An optional icon displayed before the label text.
|
|
23
|
+
class Item < ViewComponent::Base
|
|
24
|
+
renders_one :icon
|
|
25
|
+
|
|
26
|
+
attr_reader :href, :options
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def classes
|
|
30
|
+
[
|
|
31
|
+
"flex", "items-center", "px-2", "py-1.5", "text-body",
|
|
32
|
+
"rounded-base", "hover:bg-neutral-tertiary", "hover:text-fg-brand", "group"
|
|
33
|
+
]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param class [Array<String>] Additional CSS classes for the link element.
|
|
38
|
+
# @param href [String] The URL for the navigation link.
|
|
39
|
+
# @param options [Hash] Additional HTML attributes for the link element.
|
|
40
|
+
def initialize(href:, class: nil, **options)
|
|
41
|
+
super()
|
|
42
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
43
|
+
@href = href
|
|
44
|
+
@options = options
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def call
|
|
48
|
+
content_tag(:li) do
|
|
49
|
+
link_options = {class: link_classes}.merge(options)
|
|
50
|
+
content_tag(:a, href: href, **link_options) do
|
|
51
|
+
concat(icon) if icon?
|
|
52
|
+
concat(content_tag(:span, content, class: label_classes))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def label_classes
|
|
60
|
+
"ms-3" if icon?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def link_classes
|
|
64
|
+
self.class.classes + @class
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class Sidebar
|
|
5
|
+
# Renders the navigation list for a sidebar.
|
|
6
|
+
#
|
|
7
|
+
# This component renders a +<ul>+ with navigation items. It can be used
|
|
8
|
+
# inside a {Flowbite::Sidebar} for a fixed-position sidebar, or standalone
|
|
9
|
+
# in any layout that needs sidebar-style navigation.
|
|
10
|
+
#
|
|
11
|
+
# @example Inside a Sidebar
|
|
12
|
+
# <%= render(Flowbite::Sidebar.new) do %>
|
|
13
|
+
# <%= render(Flowbite::Sidebar::Navigation.new) do |nav| %>
|
|
14
|
+
# <% nav.with_item do %>
|
|
15
|
+
# <%= render(Flowbite::Sidebar::Item.new(href: "/")) { "Home" } %>
|
|
16
|
+
# <% end %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
#
|
|
20
|
+
# @example Standalone
|
|
21
|
+
# <%= render(Flowbite::Sidebar::Navigation.new) do |nav| %>
|
|
22
|
+
# <% nav.with_item do %>
|
|
23
|
+
# <%= render(Flowbite::Sidebar::Item.new(href: "/")) { "Home" } %>
|
|
24
|
+
# <% end %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
class Navigation < ViewComponent::Base
|
|
27
|
+
renders_many :items
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def classes
|
|
31
|
+
["space-y-2", "font-medium"]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param class [Array<String>] Additional CSS classes for the list element.
|
|
36
|
+
# @param options [Hash] Additional HTML options for the list element.
|
|
37
|
+
def initialize(class: nil, **options)
|
|
38
|
+
super()
|
|
39
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
40
|
+
@options = options
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call
|
|
44
|
+
content_tag(:ul, list_options) do
|
|
45
|
+
items.each do |item|
|
|
46
|
+
concat(item)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def list_classes
|
|
54
|
+
self.class.classes + @class
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def list_options
|
|
58
|
+
{class: list_classes}.merge(@options)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# Renders a fixed-position sidebar container.
|
|
5
|
+
#
|
|
6
|
+
# Use {Flowbite::Sidebar} as the outer shell and
|
|
7
|
+
# {Flowbite::Sidebar::Navigation} inside it to render the list of
|
|
8
|
+
# {Flowbite::Sidebar::Item}s.
|
|
9
|
+
#
|
|
10
|
+
# @example Usage
|
|
11
|
+
# <%= render(Flowbite::Sidebar.new) do %>
|
|
12
|
+
# <%= render(Flowbite::Sidebar::Navigation.new) do |nav| %>
|
|
13
|
+
# <% nav.with_item do %>
|
|
14
|
+
# <%= render(Flowbite::Sidebar::Item.new(href: "/dashboard")) { "Dashboard" } %>
|
|
15
|
+
# <% end %>
|
|
16
|
+
# <% end %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
#
|
|
19
|
+
# @see https://flowbite.com/docs/components/sidebar/
|
|
20
|
+
# @lookbook_embed SidebarPreview
|
|
21
|
+
class Sidebar < ViewComponent::Base
|
|
22
|
+
class << self
|
|
23
|
+
def classes
|
|
24
|
+
[
|
|
25
|
+
"fixed", "top-0", "left-0", "z-40", "w-64", "h-screen",
|
|
26
|
+
"transition-transform", "-translate-x-full", "sm:translate-x-0"
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param class [Array<String>] Additional CSS classes for the sidebar
|
|
32
|
+
# container.
|
|
33
|
+
# @param options [Hash] Additional HTML options for the sidebar container.
|
|
34
|
+
def initialize(class: nil, **options)
|
|
35
|
+
super()
|
|
36
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
37
|
+
@options = options
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def call
|
|
41
|
+
content_tag(:aside, aside_options) do
|
|
42
|
+
content_tag(:div, class: wrapper_classes) do
|
|
43
|
+
content
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def aside_classes
|
|
51
|
+
self.class.classes + @class
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def aside_options
|
|
55
|
+
{class: aside_classes, "aria-label": "Sidebar"}.merge(@options)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def wrapper_classes
|
|
59
|
+
["h-full", "px-3", "py-4", "overflow-y-auto", "bg-neutral-primary-soft"]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%= tag.div(role: "status", **options) do %>
|
|
2
|
+
<svg
|
|
3
|
+
aria-hidden="true"
|
|
4
|
+
class="<%= classes.join(" ") %>"
|
|
5
|
+
fill="none"
|
|
6
|
+
viewBox="0 0 100 101"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
11
|
+
fill="currentColor"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<path
|
|
15
|
+
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
|
|
19
|
+
<span class="sr-only"><%= message %></span>
|
|
20
|
+
<% end %>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
# Renders a loading spinner.
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# <%= render(Flowbite::Spinner.new) %>
|
|
8
|
+
#
|
|
9
|
+
# @example With a custom style and size
|
|
10
|
+
# <%= render(Flowbite::Spinner.new(style: :success, size: :lg)) %>
|
|
11
|
+
#
|
|
12
|
+
# @see https://flowbite.com/docs/components/spinner/
|
|
13
|
+
# @lookbook_embed SpinnerPreview
|
|
14
|
+
class Spinner < ViewComponent::Base
|
|
15
|
+
SIZES = {
|
|
16
|
+
xs: ["w-4", "h-4"],
|
|
17
|
+
sm: ["w-6", "h-6"],
|
|
18
|
+
default: ["w-8", "h-8"],
|
|
19
|
+
lg: ["w-10", "h-10"],
|
|
20
|
+
xl: ["w-12", "h-12"]
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def classes(size: :default, style: :brand)
|
|
25
|
+
style = styles.fetch(style)
|
|
26
|
+
["text-neutral-tertiary", "animate-spin"] + style.fetch(:default) + sizes.fetch(size)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sizes
|
|
30
|
+
SIZES
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def styles
|
|
34
|
+
Flowbite::Styles.from_hash(
|
|
35
|
+
{
|
|
36
|
+
brand: {default: ["fill-brand"]},
|
|
37
|
+
danger: {default: ["fill-danger"]},
|
|
38
|
+
success: {default: ["fill-success"]},
|
|
39
|
+
warning: {default: ["fill-warning"]}
|
|
40
|
+
}.freeze
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_reader :message, :options
|
|
46
|
+
|
|
47
|
+
# @param class [String, Array<String>] Additional CSS classes for the spinner icon.
|
|
48
|
+
# @param message [String] Text for screen readers announcing the loading state.
|
|
49
|
+
# @param options [Hash] Additional HTML options for the container element.
|
|
50
|
+
# @param size [Symbol] The size of the spinner (:xs, :sm, :default, :lg, :xl).
|
|
51
|
+
# @param style [Symbol] The color style of the spinner (:brand, :danger, :success, :warning).
|
|
52
|
+
def initialize(class: nil, message: "Loading...", size: :default, style: :brand, **options)
|
|
53
|
+
@class = Array.wrap(binding.local_variable_get(:class))
|
|
54
|
+
@message = message
|
|
55
|
+
@size = size
|
|
56
|
+
@style = style
|
|
57
|
+
@options = options
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def classes
|
|
63
|
+
self.class.classes(size: @size, style: @style) + @class
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= tag.button(**button_options) do %>
|
|
2
|
+
<svg
|
|
3
|
+
aria-hidden="true"
|
|
4
|
+
class="w-3 h-3"
|
|
5
|
+
fill="none"
|
|
6
|
+
viewBox="0 0 14 14"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path
|
|
10
|
+
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
stroke-linecap="round"
|
|
13
|
+
stroke-linejoin="round"
|
|
14
|
+
stroke-width="2"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowbite
|
|
4
|
+
class Toast
|
|
5
|
+
# Renders the close button of a toast notification.
|
|
6
|
+
#
|
|
7
|
+
# @param options [Hash] Additional HTML options for the button element.
|
|
8
|
+
# These are merged over the default options.
|
|
9
|
+
class CloseButton < ViewComponent::Base
|
|
10
|
+
class << self
|
|
11
|
+
# Styles from https://flowbite.com/docs/components/toast/#default-toast
|
|
12
|
+
# rubocop:disable Layout/LineLength
|
|
13
|
+
def classes
|
|
14
|
+
["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"]
|
|
15
|
+
end
|
|
16
|
+
# rubocop:enable Layout/LineLength
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(**options)
|
|
20
|
+
@options = options || {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def button_options
|
|
24
|
+
{
|
|
25
|
+
aria: {label: "Close"},
|
|
26
|
+
class: self.class.classes,
|
|
27
|
+
type: "button"
|
|
28
|
+
}.merge(@options)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<div class="<%= container_classes.join(" ") %>">
|
|
2
|
-
<svg
|
|
3
|
-
|
|
2
|
+
<svg
|
|
3
|
+
aria-hidden="true"
|
|
4
|
+
class="w-4 h-4"
|
|
5
|
+
fill="currentColor"
|
|
6
|
+
viewBox="0 0 20 20"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
>
|
|
9
|
+
<path d="<%= svg_path %>" />
|
|
4
10
|
</svg>
|
|
5
11
|
</div>
|