daisy_components 0.1.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 +7 -0
- data/.cursor/rules/how-to-write-tests.mdc +67 -0
- data/.cursor/rules/preview-file-stucture.mdc +95 -0
- data/.cursorrules +64 -0
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/ci.yml +65 -0
- data/.gitignore +13 -0
- data/.vscode/launch.json +55 -0
- data/.vscode/settings.json +44 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +335 -0
- data/MIT-LICENSE +20 -0
- data/README.md +64 -0
- data/Rakefile +7 -0
- data/app/assets/images/daisy_components/.keep +0 -0
- data/app/assets/stylesheets/daisy_ui/application.css +15 -0
- data/app/components/daisy_ui/actions/button.rb +262 -0
- data/app/components/daisy_ui/actions/dropdown.rb +125 -0
- data/app/components/daisy_ui/actions/swap.rb +126 -0
- data/app/components/daisy_ui/base_component.rb +29 -0
- data/app/components/daisy_ui/data_display/accordion.rb +128 -0
- data/app/components/daisy_ui/data_display/accordion_item.rb +121 -0
- data/app/components/daisy_ui/data_display/avatar.rb +131 -0
- data/app/components/daisy_ui/data_display/avatar_group.rb +102 -0
- data/app/components/daisy_ui/data_display/badge.rb +126 -0
- data/app/components/daisy_ui/data_display/card/actions.rb +94 -0
- data/app/components/daisy_ui/data_display/card/body.rb +113 -0
- data/app/components/daisy_ui/data_display/card/figure.rb +44 -0
- data/app/components/daisy_ui/data_display/card/title.rb +56 -0
- data/app/components/daisy_ui/data_display/card.rb +157 -0
- data/app/components/daisy_ui/data_display/chat.rb +92 -0
- data/app/components/daisy_ui/data_display/chat_bubble/metadata.rb +71 -0
- data/app/components/daisy_ui/data_display/chat_bubble.rb +166 -0
- data/app/components/daisy_ui/divider.rb +9 -0
- data/app/components/daisy_ui/item.rb +20 -0
- data/app/components/daisy_ui/title.rb +9 -0
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/daisy_ui/application_controller.rb +6 -0
- data/app/helpers/daisy_ui/application_helper.rb +6 -0
- data/app/helpers/daisy_ui/icons_helper.rb +296 -0
- data/app/views/layouts/daisyui/application.html.erb +17 -0
- data/bin/parse_coverage.rb +59 -0
- data/bin/rails +57 -0
- data/bin/rubocop +10 -0
- data/bin/scrape_component +86 -0
- data/daisy_components.gemspec +33 -0
- data/docs/assets/2025-01_screeshot_1.png +0 -0
- data/docs/assets/2025-01_screeshot_2.png +0 -0
- data/docs/assets/2025-01_screeshot_3.png +0 -0
- data/lib/daisy_components.rb +5 -0
- data/lib/daisy_ui/engine.rb +51 -0
- data/lib/daisy_ui/version.rb +5 -0
- data/lib/daisy_ui.rb +13 -0
- data/lib/tasks/daisy_ui_tasks.rake +6 -0
- metadata +112 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# Avatar group component that displays a collection of avatars with customizable styling
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# <%= render(AvatarGroupComponent.new) do |group| %>
|
|
8
|
+
# <% group.with_avatar(img_src: "path/to/image.jpg", img_alt: "User 1") %>
|
|
9
|
+
# <% group.with_avatar(img_src: "path/to/image.jpg", img_alt: "User 2") %>
|
|
10
|
+
# <% end %>
|
|
11
|
+
#
|
|
12
|
+
# @example With max display and counter
|
|
13
|
+
# <%= render(AvatarGroupComponent.new(max_display: 2)) do |group| %>
|
|
14
|
+
# <% group.with_avatar(img_src: "path/to/image.jpg", img_alt: "User 1") %>
|
|
15
|
+
# <% group.with_avatar(img_src: "path/to/image.jpg", img_alt: "User 2") %>
|
|
16
|
+
# <% group.with_avatar(img_src: "path/to/image.jpg", img_alt: "User 3") %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
class AvatarGroup < DaisyUI::BaseComponent
|
|
19
|
+
SIZES = Avatar::SIZES
|
|
20
|
+
SHAPES = Avatar::SHAPES
|
|
21
|
+
DEFAULT_SPACING = 1.5
|
|
22
|
+
|
|
23
|
+
renders_many :avatars, lambda { |**system_arguments|
|
|
24
|
+
Avatar.new(
|
|
25
|
+
size: @size,
|
|
26
|
+
shape: @shape&.to_sym,
|
|
27
|
+
inner_class: system_arguments.delete(:inner_class),
|
|
28
|
+
**system_arguments
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Initializes a new Avatar Group component
|
|
33
|
+
#
|
|
34
|
+
# @param size [Symbol, nil] Size of the avatars. Must be one of: w8, w12, w16, w20, w24, w32
|
|
35
|
+
# @param shape [Symbol, nil] Shape of the avatars. Must be one of: circle, squircle, hexagon, triangle
|
|
36
|
+
# @param spacing [Float] Space between avatars in rem units (default: 1.5)
|
|
37
|
+
# @param max_display [Integer, nil] Maximum number of avatars to display before showing a counter
|
|
38
|
+
# @param system_arguments [Hash] Additional HTML attributes to be applied to the wrapper element
|
|
39
|
+
def initialize(size: nil, shape: nil, spacing: DEFAULT_SPACING, max_display: nil, **system_arguments)
|
|
40
|
+
@size = size
|
|
41
|
+
@shape = shape
|
|
42
|
+
@spacing = spacing.to_f
|
|
43
|
+
@max_display = max_display&.to_i
|
|
44
|
+
|
|
45
|
+
super(**system_arguments)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [String] Rendered HTML for the avatar group
|
|
49
|
+
def call
|
|
50
|
+
tag.div(**html_attributes) do
|
|
51
|
+
content = []
|
|
52
|
+
content.concat(displayed_avatars)
|
|
53
|
+
content << render_counter if remaining_count
|
|
54
|
+
safe_join(content)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def default_classes
|
|
61
|
+
class_names(
|
|
62
|
+
'avatar-group',
|
|
63
|
+
spacing_class,
|
|
64
|
+
system_arguments[:class]
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def html_attributes
|
|
69
|
+
attrs = system_arguments.except(:class)
|
|
70
|
+
attrs[:class] = default_classes
|
|
71
|
+
attrs
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def displayed_avatars
|
|
75
|
+
return avatars if avatars.empty? || !@max_display
|
|
76
|
+
|
|
77
|
+
avatars.first(@max_display)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def remaining_count
|
|
81
|
+
return unless @max_display && avatars.size > @max_display
|
|
82
|
+
|
|
83
|
+
avatars.size - @max_display
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def render_counter
|
|
87
|
+
Avatar.new(
|
|
88
|
+
size: @size,
|
|
89
|
+
shape: @shape,
|
|
90
|
+
placeholder_text: "+#{remaining_count}",
|
|
91
|
+
inner_class: 'bg-neutral text-neutral-content'
|
|
92
|
+
).call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def spacing_class
|
|
96
|
+
# Convert rem to tailwind spacing units (1rem = 4 units)
|
|
97
|
+
# Clamp between 0 and 16 (4rem) to ensure valid Tailwind classes
|
|
98
|
+
rem = [(@spacing * 4).round, 0].max.clamp(0, 16)
|
|
99
|
+
"-space-x-#{rem}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# Badge component implementing DaisyUI's badge styles
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# <%= render(BadgeComponent.new(text: "Badge")) %>
|
|
8
|
+
#
|
|
9
|
+
# @example Primary variant
|
|
10
|
+
# <%= render(BadgeComponent.new(text: "Primary", color: :primary)) %>
|
|
11
|
+
#
|
|
12
|
+
# @example Outline style
|
|
13
|
+
# <%= render(BadgeComponent.new(text: "Outline", variant: :outline)) %>
|
|
14
|
+
#
|
|
15
|
+
# @example Small size with icon
|
|
16
|
+
# <%= render(BadgeComponent.new(
|
|
17
|
+
# text: "Icon",
|
|
18
|
+
# size: :sm,
|
|
19
|
+
# icon: helpers.check_icon
|
|
20
|
+
# )) %>
|
|
21
|
+
#
|
|
22
|
+
# @example Soft style with color
|
|
23
|
+
# <%= render(BadgeComponent.new(
|
|
24
|
+
# text: "Soft",
|
|
25
|
+
# variant: :soft,
|
|
26
|
+
# color: :primary
|
|
27
|
+
# )) %>
|
|
28
|
+
#
|
|
29
|
+
# @example With block content
|
|
30
|
+
# <%= render(BadgeComponent.new) do %>
|
|
31
|
+
# Complex <strong>content</strong>
|
|
32
|
+
# <% end %>
|
|
33
|
+
#
|
|
34
|
+
# @example With custom tag type
|
|
35
|
+
# <%= render(BadgeComponent.new(text: "Badge", tag_type: :span)) %>
|
|
36
|
+
class Badge < DaisyUI::BaseComponent
|
|
37
|
+
# Available badge colors from DaisyUI
|
|
38
|
+
COLORS = {
|
|
39
|
+
primary: 'badge-primary',
|
|
40
|
+
secondary: 'badge-secondary',
|
|
41
|
+
accent: 'badge-accent',
|
|
42
|
+
neutral: 'badge-neutral',
|
|
43
|
+
ghost: 'badge-ghost',
|
|
44
|
+
info: 'badge-info',
|
|
45
|
+
success: 'badge-success',
|
|
46
|
+
warning: 'badge-warning',
|
|
47
|
+
error: 'badge-error'
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
# Available badge sizes from DaisyUI
|
|
51
|
+
SIZES = {
|
|
52
|
+
xl: 'badge-xl',
|
|
53
|
+
lg: 'badge-lg',
|
|
54
|
+
md: 'badge-md',
|
|
55
|
+
sm: 'badge-sm',
|
|
56
|
+
xs: 'badge-xs'
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
# Available badge variants from DaisyUI
|
|
60
|
+
VARIANTS = {
|
|
61
|
+
outline: 'badge-outline',
|
|
62
|
+
soft: 'badge-soft',
|
|
63
|
+
dash: 'badge-dash',
|
|
64
|
+
ghost: 'badge-ghost'
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
# @param text [String] The text content to display inside the badge
|
|
68
|
+
# @param color [String] Visual style of the badge
|
|
69
|
+
# (neutral/primary/secondary/accent/info/success/warning/error/ghost)
|
|
70
|
+
# @param size [String] Size of the badge (xl/lg/md/sm/xs)
|
|
71
|
+
# @param variant [String] Variant of the badge (outline/soft/dash/ghost)
|
|
72
|
+
# @param icon [String] SVG icon to display before the text
|
|
73
|
+
# @param tag_type [Symbol] HTML tag to use for the badge (default: :div)
|
|
74
|
+
# @param system_arguments [Hash] Additional HTML attributes to be applied to the badge
|
|
75
|
+
def initialize(
|
|
76
|
+
text = nil,
|
|
77
|
+
color: nil,
|
|
78
|
+
size: nil,
|
|
79
|
+
variant: nil,
|
|
80
|
+
icon: nil,
|
|
81
|
+
tag_type: :div,
|
|
82
|
+
**system_arguments
|
|
83
|
+
)
|
|
84
|
+
@text = text
|
|
85
|
+
@color = build_argument(color, COLORS, 'color')
|
|
86
|
+
@size = build_argument(size, SIZES, 'size')
|
|
87
|
+
@variant = build_argument(variant, VARIANTS, 'variant')
|
|
88
|
+
@icon = icon
|
|
89
|
+
@tag_type = tag_type
|
|
90
|
+
|
|
91
|
+
super(**system_arguments)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def call
|
|
95
|
+
tag.public_send(@tag_type, **html_attributes) do
|
|
96
|
+
safe_join([render_icon, content || @text].compact)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
delegate :to_s, to: :call
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def render_icon
|
|
105
|
+
return unless @icon
|
|
106
|
+
|
|
107
|
+
helpers.sanitize(@icon, tags: %w[svg path g circle],
|
|
108
|
+
attributes: %w[class viewBox fill stroke d])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def computed_classes
|
|
112
|
+
modifiers = ['badge']
|
|
113
|
+
modifiers << @variant
|
|
114
|
+
modifiers << @color
|
|
115
|
+
modifiers << @size
|
|
116
|
+
|
|
117
|
+
class_names(modifiers, system_arguments[:class])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def html_attributes
|
|
121
|
+
attrs = system_arguments.except(:class)
|
|
122
|
+
attrs[:class] = computed_classes
|
|
123
|
+
attrs
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class Card
|
|
5
|
+
# Actions component for the card, handling action buttons layout
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
9
|
+
# <% component.with_body do |body| %>
|
|
10
|
+
# <% body.with_actions do %>
|
|
11
|
+
# <%= render(ButtonComponent.new(text: "Action")) %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
# <% end %>
|
|
14
|
+
# <% end %>
|
|
15
|
+
#
|
|
16
|
+
# @example With multiple buttons
|
|
17
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
18
|
+
# <% component.with_body do |body| %>
|
|
19
|
+
# <% body.with_actions(justify: :between) do |actions| %>
|
|
20
|
+
# <% actions.with_button(text: "Cancel", variant: :ghost) %>
|
|
21
|
+
# <% actions.with_button(text: "Submit", color: :primary) %>
|
|
22
|
+
# <% end %>
|
|
23
|
+
# <% end %>
|
|
24
|
+
# <% end %>
|
|
25
|
+
#
|
|
26
|
+
# @example With custom buttons
|
|
27
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
28
|
+
# <% component.with_body do |body| %>
|
|
29
|
+
# <% body.with_actions(justify: :end) do %>
|
|
30
|
+
# <%= render(ButtonComponent.new(text: "Delete", color: :error)) %>
|
|
31
|
+
# <%= render(ButtonComponent.new(text: "Save", color: :primary)) %>
|
|
32
|
+
# <% end %>
|
|
33
|
+
# <% end %>
|
|
34
|
+
# <% end %>
|
|
35
|
+
class Actions < BaseComponent
|
|
36
|
+
# Available justification options for actions layout
|
|
37
|
+
JUSTIFY_OPTIONS = {
|
|
38
|
+
start: 'justify-start',
|
|
39
|
+
end: 'justify-end',
|
|
40
|
+
center: 'justify-center',
|
|
41
|
+
between: 'justify-between',
|
|
42
|
+
around: 'justify-around',
|
|
43
|
+
evenly: 'justify-evenly'
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
# Renders multiple actions (buttons or badges)
|
|
47
|
+
renders_many :actions, types: {
|
|
48
|
+
button: { renders: DaisyUI::Button, as: :button },
|
|
49
|
+
badge: { renders: DaisyUI::Badge, as: :badge }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# @param justify [Symbol] Justification for action buttons layout
|
|
53
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
54
|
+
def initialize(justify: nil, **system_arguments)
|
|
55
|
+
@justify = build_justify(justify)
|
|
56
|
+
super(**system_arguments)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def call
|
|
60
|
+
tag.div(**html_attributes) do
|
|
61
|
+
if actions.any?
|
|
62
|
+
safe_join(actions)
|
|
63
|
+
else
|
|
64
|
+
content
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def build_justify(key)
|
|
72
|
+
return nil unless key
|
|
73
|
+
|
|
74
|
+
class_name = JUSTIFY_OPTIONS[key.to_sym]
|
|
75
|
+
return class_name if class_name
|
|
76
|
+
|
|
77
|
+
raise ArgumentError, "Invalid justify: #{key}. Must be one of: #{JUSTIFY_OPTIONS.keys.join(', ')}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def computed_classes
|
|
81
|
+
modifiers = ['card-actions']
|
|
82
|
+
modifiers << @justify if @justify
|
|
83
|
+
|
|
84
|
+
class_names(modifiers, system_arguments[:class])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def html_attributes
|
|
88
|
+
system_arguments.merge(
|
|
89
|
+
class: computed_classes
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class Card
|
|
5
|
+
# Body component for the card, containing title, description and actions
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
9
|
+
# <% component.with_body do |body| %>
|
|
10
|
+
# <% body.with_title { "Title" } %>
|
|
11
|
+
# <% body.with_description { "Description" } %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
# <% end %>
|
|
14
|
+
#
|
|
15
|
+
# @example With simple button
|
|
16
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
17
|
+
# <% component.with_body(
|
|
18
|
+
# title: "Title",
|
|
19
|
+
# description: "Description",
|
|
20
|
+
# button: { text: "Action", color: :primary, justify: :end }
|
|
21
|
+
# ) %>
|
|
22
|
+
# <% end %>
|
|
23
|
+
#
|
|
24
|
+
# @example With multiple buttons
|
|
25
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
26
|
+
# <% component.with_body do |body| %>
|
|
27
|
+
# <% body.with_title { "Title" } %>
|
|
28
|
+
# <% body.with_actions(justify: :between) do |actions| %>
|
|
29
|
+
# <% actions.with_button(text: "Cancel", variant: :ghost) %>
|
|
30
|
+
# <% actions.with_bage(text: "Submit", color: :primary) %>
|
|
31
|
+
# <% end %>
|
|
32
|
+
# <% end %>
|
|
33
|
+
# <% end %>
|
|
34
|
+
class Body < BaseComponent
|
|
35
|
+
# Title is a component slot that renders TitleComponent
|
|
36
|
+
renders_one :title, DaisyUI::Card::Title
|
|
37
|
+
|
|
38
|
+
# Actions is a component slot that renders ActionsComponent
|
|
39
|
+
renders_one :actions, DaisyUI::Card::Actions
|
|
40
|
+
|
|
41
|
+
# Description is a content slot that can contain any content
|
|
42
|
+
renders_one :description
|
|
43
|
+
|
|
44
|
+
# @param variant [Symbol] Card variant affecting body layout
|
|
45
|
+
# @param description [String] Card description text
|
|
46
|
+
# @param title [String] Card title text
|
|
47
|
+
# @param button [Hash] Simple button configuration
|
|
48
|
+
# @option button [String] :text Button text
|
|
49
|
+
# @option button [Symbol] :color Button color
|
|
50
|
+
# @option button [Symbol] :variant Button variant
|
|
51
|
+
# @option button [Symbol] :size Button size
|
|
52
|
+
# @option button [Symbol] :justify Button justification
|
|
53
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
54
|
+
def initialize(variant: nil,
|
|
55
|
+
description: nil,
|
|
56
|
+
title: nil,
|
|
57
|
+
button: nil,
|
|
58
|
+
**system_arguments)
|
|
59
|
+
@variant = variant
|
|
60
|
+
@description = description
|
|
61
|
+
@title = title
|
|
62
|
+
@button = button
|
|
63
|
+
super(**system_arguments)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def call
|
|
67
|
+
tag.div(**html_attributes) do
|
|
68
|
+
safe_join([
|
|
69
|
+
title,
|
|
70
|
+
render_description,
|
|
71
|
+
actions
|
|
72
|
+
].compact)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def before_render
|
|
77
|
+
with_title(@title) if @title && !title?
|
|
78
|
+
with_description { tag.p(@description) } if @description && !description?
|
|
79
|
+
setup_button if @button && !actions?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def render_description
|
|
85
|
+
return description if description?
|
|
86
|
+
|
|
87
|
+
tag.p(@description) if @description
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def setup_button
|
|
91
|
+
button_config = @button.symbolize_keys
|
|
92
|
+
justify = button_config.delete(:justify)
|
|
93
|
+
|
|
94
|
+
with_actions(justify: justify) do |actions|
|
|
95
|
+
actions.with_button(**button_config)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def computed_classes
|
|
100
|
+
modifiers = %w[card-body]
|
|
101
|
+
modifiers << 'flex flex-col justify-between' if @variant == :side
|
|
102
|
+
|
|
103
|
+
class_names(modifiers, system_arguments[:class])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def html_attributes
|
|
107
|
+
system_arguments.merge(
|
|
108
|
+
class: computed_classes
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class Card
|
|
5
|
+
# Figure component for the card, handling image display
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage with URL
|
|
8
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
9
|
+
# <% component.with_figure(img_url: "https://picsum.photos/400/200") %>
|
|
10
|
+
# <% end %>
|
|
11
|
+
#
|
|
12
|
+
# @example Custom image tag
|
|
13
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
14
|
+
# <% component.with_figure do |figure| %>
|
|
15
|
+
# <% figure.with_image do %>
|
|
16
|
+
# <%= image_tag "custom.jpg", class: "rounded-xl" %>
|
|
17
|
+
# <% end %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
class Figure < BaseComponent
|
|
21
|
+
# Image is a content slot that can contain any image content
|
|
22
|
+
renders_one :image
|
|
23
|
+
|
|
24
|
+
# @param img_url [String] URL for the card image
|
|
25
|
+
# @param alt [String] Alt text for the image
|
|
26
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
27
|
+
def initialize(img_url: nil, img_alt: nil, **system_arguments)
|
|
28
|
+
@img_url = img_url
|
|
29
|
+
@img_alt = img_alt
|
|
30
|
+
super(**system_arguments)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def before_render
|
|
34
|
+
with_image { tag.img(src: @img_url, alt: @img_alt) } if @img_url && !image?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def call
|
|
38
|
+
tag.figure(**system_arguments) do
|
|
39
|
+
safe_join([image, content].compact)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class Card
|
|
5
|
+
# Title component for the card, handling the title display
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
9
|
+
# <% component.with_body do |body| %>
|
|
10
|
+
# <% body.with_title { "Card Title" } %>
|
|
11
|
+
# <% end %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
# @example With custom tag
|
|
15
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
16
|
+
# <% component.with_body do |body| %>
|
|
17
|
+
# <% body.with_title(tag_name: :h1) { "Large Title" } %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
#
|
|
21
|
+
# @example With text parameter
|
|
22
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
23
|
+
# <% component.with_body do |body| %>
|
|
24
|
+
# <% body.with_title("Simple Title") %>
|
|
25
|
+
# <% end %>
|
|
26
|
+
# <% end %>
|
|
27
|
+
class Title < BaseComponent
|
|
28
|
+
# @param text [String] The title text (optional if block given)
|
|
29
|
+
# @param tag_name [Symbol] HTML tag to use for the title
|
|
30
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
31
|
+
def initialize(text = nil, tag_name: :h2, **system_arguments)
|
|
32
|
+
@text = text
|
|
33
|
+
@tag_name = tag_name
|
|
34
|
+
super(**system_arguments)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def call
|
|
38
|
+
content_tag(@tag_name, **html_attributes) do
|
|
39
|
+
content || @text
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def computed_classes
|
|
46
|
+
class_names('card-title', system_arguments[:class])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def html_attributes
|
|
50
|
+
system_arguments.merge(
|
|
51
|
+
class: computed_classes
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# Card component for displaying content in a contained card format
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage with simple button
|
|
7
|
+
# <%= render(CardComponent.new(
|
|
8
|
+
# title: "Card Title",
|
|
9
|
+
# description: "Card description here",
|
|
10
|
+
# button: { text: "Action", color: :primary, justify: :end }
|
|
11
|
+
# )) %>
|
|
12
|
+
#
|
|
13
|
+
# @example With figure/image
|
|
14
|
+
# <%= render(CardComponent.new(
|
|
15
|
+
# img_url: "https://picsum.photos/400/200"
|
|
16
|
+
# )) %>
|
|
17
|
+
#
|
|
18
|
+
# @example Bordered variant
|
|
19
|
+
# <%= render(CardComponent.new(variant: :bordered)) do |component| %>
|
|
20
|
+
# <% component.with_body(title: "Bordered Card") %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
#
|
|
23
|
+
# @example Side image variant
|
|
24
|
+
# <%= render(CardComponent.new(
|
|
25
|
+
# variant: :side,
|
|
26
|
+
# img_url: "https://picsum.photos/200/400"
|
|
27
|
+
# )) do |component| %>
|
|
28
|
+
# <% component.with_body do |body| %>
|
|
29
|
+
# <% body.with_title { "Side Image" } %>
|
|
30
|
+
# <% end %>
|
|
31
|
+
# <% end %>
|
|
32
|
+
#
|
|
33
|
+
# @example With actions
|
|
34
|
+
# <%= render(CardComponent.new) do |component| %>
|
|
35
|
+
# <% component.with_body do |body| %>
|
|
36
|
+
# <% body.with_title { "Card with Actions" } %>
|
|
37
|
+
# <% body.with_actions(justify: :between) do %>
|
|
38
|
+
# <%= render(ButtonComponent.new(text: "Action")) %>
|
|
39
|
+
# <% end %>
|
|
40
|
+
# <% end %>
|
|
41
|
+
# <% end %>
|
|
42
|
+
class Card < BaseComponent
|
|
43
|
+
# Available card variants
|
|
44
|
+
VARIANTS = {
|
|
45
|
+
compact: 'card-compact',
|
|
46
|
+
side: 'card-side',
|
|
47
|
+
side_responsive: 'lg:card-side',
|
|
48
|
+
bordered: 'card-border',
|
|
49
|
+
dash: 'card-dash'
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
COLORS = {
|
|
53
|
+
primary: 'bg-primary text-primary-content',
|
|
54
|
+
secondary: 'bg-secondary text-secondary-content',
|
|
55
|
+
accent: 'bg-accent text-accent-content',
|
|
56
|
+
neutral: 'bg-neutral text-neutral-content',
|
|
57
|
+
base: 'bg-base-100'
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
SIZES = {
|
|
61
|
+
xs: 'card-xs',
|
|
62
|
+
sm: 'card-sm',
|
|
63
|
+
md: 'card-md',
|
|
64
|
+
lg: 'card-lg',
|
|
65
|
+
xl: 'card-xl'
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
renders_one :figure, lambda { |**system_arguments|
|
|
69
|
+
Figure.new(img_url: @img_url, img_alt: @img_alt, **system_arguments)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
renders_one :body, lambda { |**system_arguments|
|
|
73
|
+
Body.new(
|
|
74
|
+
variant: @variant,
|
|
75
|
+
title: @title,
|
|
76
|
+
description: @description,
|
|
77
|
+
button: @button,
|
|
78
|
+
**system_arguments
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# @param variant [Symbol] Card layout variant (:compact, :side, :bordered)
|
|
83
|
+
# @param image_full [Boolean] Makes the image cover the full card
|
|
84
|
+
# @param title [String] Card title (optional)
|
|
85
|
+
# @param description [String] Card description (optional)
|
|
86
|
+
# @param button [Hash] Simple button configuration (optional)
|
|
87
|
+
# @option button [String] :text Button text
|
|
88
|
+
# @option button [Symbol] :color Button color (primary, secondary, etc)
|
|
89
|
+
# @option button [Symbol] :variant Button variant (outline, soft, etc)
|
|
90
|
+
# @option button [Symbol] :size Button size (xs, sm, md, lg, xl)
|
|
91
|
+
# @option button [Symbol] :justify Button justification (start, end, center, between, around, evenly)
|
|
92
|
+
# @param img_url [String] URL for the card image (optional)
|
|
93
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
94
|
+
def initialize(variant: nil,
|
|
95
|
+
image_full: false,
|
|
96
|
+
title: nil,
|
|
97
|
+
description: nil,
|
|
98
|
+
button: nil,
|
|
99
|
+
img_url: nil,
|
|
100
|
+
img_alt: nil,
|
|
101
|
+
bottom_image: false,
|
|
102
|
+
shadow: false,
|
|
103
|
+
size: nil,
|
|
104
|
+
color: :base,
|
|
105
|
+
**system_arguments)
|
|
106
|
+
@variant = build_argument(variant, VARIANTS, 'variant')
|
|
107
|
+
@color = build_argument(color, COLORS, 'color')
|
|
108
|
+
@size = build_argument(size, SIZES, 'size')
|
|
109
|
+
@image_full = image_full
|
|
110
|
+
@title = title
|
|
111
|
+
@description = description
|
|
112
|
+
@button = button
|
|
113
|
+
@img_url = img_url
|
|
114
|
+
@img_alt = img_alt
|
|
115
|
+
@shadow = shadow
|
|
116
|
+
@bottom_image = bottom_image
|
|
117
|
+
super(**system_arguments)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def before_render
|
|
121
|
+
with_figure if @img_url && !figure?
|
|
122
|
+
with_body unless body?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def call
|
|
126
|
+
tag.div(**html_attributes) do
|
|
127
|
+
safe_join(render_in_oder)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def render_in_oder
|
|
132
|
+
if @bottom_image
|
|
133
|
+
[body, figure].compact
|
|
134
|
+
else
|
|
135
|
+
[figure, body].compact
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def computed_classes
|
|
142
|
+
modifiers = %w[card]
|
|
143
|
+
modifiers << @color if @color.present?
|
|
144
|
+
modifiers << @variant if @variant.present?
|
|
145
|
+
modifiers << 'image-full' if @image_full
|
|
146
|
+
modifiers << 'shadow-sm' if @shadow
|
|
147
|
+
modifiers << 'w-96' unless @variant.in?([VARIANTS[:side], VARIANTS[:side_responsive]])
|
|
148
|
+
modifiers << @size if @size.present?
|
|
149
|
+
class_names(modifiers, system_arguments[:class])
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def html_attributes
|
|
153
|
+
safe_args = system_arguments.except(:bottom_image)
|
|
154
|
+
safe_args.merge(class: computed_classes)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|