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,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# Chat component for displaying a list of chat messages
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# <%= render(ChatComponent.new(messages: [
|
|
8
|
+
# { text: "Hello!", user_id: 1 },
|
|
9
|
+
# { text: "Hi there!", user_id: 2 }
|
|
10
|
+
# ], current_user_id: 1)) %>
|
|
11
|
+
#
|
|
12
|
+
# @example With specific positions (overriding automatic positioning)
|
|
13
|
+
# <%= render(ChatComponent.new(messages: [
|
|
14
|
+
# { text: "Hello!", position: :start },
|
|
15
|
+
# { text: "Hi there!", position: :end }
|
|
16
|
+
# ])) %>
|
|
17
|
+
#
|
|
18
|
+
# @example With avatar, header and footer
|
|
19
|
+
# <%= render(ChatComponent.new(
|
|
20
|
+
# messages: [
|
|
21
|
+
# {
|
|
22
|
+
# text: "Hello!",
|
|
23
|
+
# user_id: 1,
|
|
24
|
+
# avatar: { img_src: "user.jpg", img_alt: "User" },
|
|
25
|
+
# header: { text: "John", time: "12:45" },
|
|
26
|
+
# footer: { text: "Delivered", time: "12:46" }
|
|
27
|
+
# }
|
|
28
|
+
# ],
|
|
29
|
+
# current_user_id: 2
|
|
30
|
+
# )) %>
|
|
31
|
+
class Chat < BaseComponent
|
|
32
|
+
# Define a slot for multiple messages
|
|
33
|
+
renders_many :bubbles, DaisyUI::ChatBubble
|
|
34
|
+
|
|
35
|
+
include ActionView::Helpers::TagHelper
|
|
36
|
+
|
|
37
|
+
# @param messages [Array<Hash>] Array of message objects to display
|
|
38
|
+
# @param current_user_id [Any] ID of the current user to determine message positions
|
|
39
|
+
# @option messages [String] :text The message text
|
|
40
|
+
# @option messages [Any] :user_id ID of the user who sent the message
|
|
41
|
+
# @option messages [Symbol] :position Optional override for the position (:start or :end)
|
|
42
|
+
# @option messages [Symbol] :color The color of the message bubble
|
|
43
|
+
# @option messages [Hash] :avatar Avatar options
|
|
44
|
+
# @option messages [Hash] :header Header options
|
|
45
|
+
# @option messages [Hash] :footer Footer options
|
|
46
|
+
def initialize(messages: [], current_user_id: nil, **system_arguments)
|
|
47
|
+
@messages = messages
|
|
48
|
+
@current_user_id = current_user_id
|
|
49
|
+
super(**system_arguments)
|
|
50
|
+
|
|
51
|
+
# Pre-populate bubbles from messages array if provided
|
|
52
|
+
@messages.each { |message| add_bubble(message) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call
|
|
56
|
+
return unless bubbles.any?
|
|
57
|
+
|
|
58
|
+
content_tag(:div, class: 'w-full') do
|
|
59
|
+
safe_join(bubbles)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def add_bubble(message)
|
|
66
|
+
position = determine_position(message)
|
|
67
|
+
|
|
68
|
+
with_bubble(
|
|
69
|
+
message[:text],
|
|
70
|
+
position: position,
|
|
71
|
+
color: message[:color],
|
|
72
|
+
avatar: message[:avatar],
|
|
73
|
+
header: message[:header],
|
|
74
|
+
footer: message[:footer]
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def determine_position(message)
|
|
79
|
+
return message[:position] if message[:position].present?
|
|
80
|
+
|
|
81
|
+
if @current_user_id.present? && message[:user_id].present?
|
|
82
|
+
message[:user_id] == @current_user_id ? :end : :start
|
|
83
|
+
else
|
|
84
|
+
:start # Default position if no positioning info is available
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def html_attributes
|
|
89
|
+
system_arguments
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class ChatBubble
|
|
5
|
+
# Component for chat bubble metadata (header/footer)
|
|
6
|
+
#
|
|
7
|
+
# @example Header usage
|
|
8
|
+
# <%= render(MetadataComponent.new(text: "John Doe", time: "12:45", type: :header)) %>
|
|
9
|
+
#
|
|
10
|
+
# @example Footer usage
|
|
11
|
+
# <%= render(MetadataComponent.new(text: "Delivered", time: "12:46", type: :footer)) %>
|
|
12
|
+
class Metadata < BaseComponent
|
|
13
|
+
TYPES = %i[header footer].freeze
|
|
14
|
+
|
|
15
|
+
TYPE_CLASSES = {
|
|
16
|
+
header: 'chat-header',
|
|
17
|
+
footer: 'chat-footer opacity-50'
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# @param text [String] The main text content
|
|
21
|
+
# @param time [String, nil] Optional timestamp
|
|
22
|
+
# @param type [Symbol] Type of metadata (:header or :footer)
|
|
23
|
+
# @param system_arguments [Hash] Additional HTML attributes
|
|
24
|
+
def initialize(text:, type:, time: nil, **system_arguments)
|
|
25
|
+
@text = text
|
|
26
|
+
@time = time
|
|
27
|
+
@type = validate_type!(type)
|
|
28
|
+
super(**system_arguments)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
tag.div(**html_attributes) do
|
|
33
|
+
safe_join([
|
|
34
|
+
@text,
|
|
35
|
+
render_time
|
|
36
|
+
].compact)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def render_time
|
|
43
|
+
return unless @time
|
|
44
|
+
|
|
45
|
+
tag.time(@time, class: time_classes)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def computed_classes
|
|
49
|
+
class_names(TYPE_CLASSES[@type], system_arguments[:class])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def html_attributes
|
|
53
|
+
system_arguments.merge(
|
|
54
|
+
class: computed_classes
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def time_classes
|
|
59
|
+
modifiers = ['text-xs opacity-50']
|
|
60
|
+
class_names(modifiers)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_type!(type)
|
|
64
|
+
type = type.to_sym
|
|
65
|
+
return type if TYPES.include?(type)
|
|
66
|
+
|
|
67
|
+
raise ArgumentError, "Invalid type: #{type}. Must be one of: #{TYPES.join(', ')}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# Chat bubble component implementing DaisyUI's chat bubble styles
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# <%= render(ChatBubbleComponent.new(text: "Hello!")) %>
|
|
8
|
+
#
|
|
9
|
+
# @example With avatar
|
|
10
|
+
# <%= render(ChatBubbleComponent.new(text: "Hello!")) do |c|
|
|
11
|
+
# <% c.with_avatar(img_src: "user.jpg", img_alt: "User") %>
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
# @example With header and footer
|
|
15
|
+
# <%= render(ChatBubbleComponent.new(text: "Hello!")) do |c|
|
|
16
|
+
# <% c.with_header(text: "John Doe", time: "12:45") %>
|
|
17
|
+
# <% c.with_footer(text: "Delivered", time: "12:46") %>
|
|
18
|
+
# <% end %>
|
|
19
|
+
#
|
|
20
|
+
# @example End position
|
|
21
|
+
# <%= render(ChatBubbleComponent.new(text: "Hello!", position: :end)) %>
|
|
22
|
+
#
|
|
23
|
+
# @example With color
|
|
24
|
+
# <%= render(ChatBubbleComponent.new(text: "Hello!", color: :primary)) %>
|
|
25
|
+
class ChatBubble < BaseComponent
|
|
26
|
+
# Available chat bubble colors from DaisyUI
|
|
27
|
+
COLORS = {
|
|
28
|
+
primary: 'chat-bubble-primary',
|
|
29
|
+
secondary: 'chat-bubble-secondary',
|
|
30
|
+
accent: 'chat-bubble-accent',
|
|
31
|
+
neutral: 'chat-bubble-neutral',
|
|
32
|
+
info: 'chat-bubble-info',
|
|
33
|
+
success: 'chat-bubble-success',
|
|
34
|
+
warning: 'chat-bubble-warning',
|
|
35
|
+
error: 'chat-bubble-error'
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
# Available positions
|
|
39
|
+
POSITIONS = {
|
|
40
|
+
start: 'chat-start',
|
|
41
|
+
end: 'chat-end'
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
renders_one :avatar, lambda { |**system_arguments|
|
|
45
|
+
size = system_arguments.delete(:size) || :w10
|
|
46
|
+
Avatar.new(**system_arguments, class: 'chat-image', size: size, shape: :circle)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
renders_one :header, lambda { |**system_arguments|
|
|
50
|
+
Metadata.new(
|
|
51
|
+
**@header_options, **system_arguments,
|
|
52
|
+
type: :header,
|
|
53
|
+
class: @header_class
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
renders_one :footer, lambda { |**system_arguments|
|
|
58
|
+
Metadata.new(
|
|
59
|
+
**@footer_options, **system_arguments,
|
|
60
|
+
type: :footer,
|
|
61
|
+
class: @footer_class
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# @param text [String] The text content to display inside the chat bubble
|
|
66
|
+
# @param position [Symbol] Position of the chat bubble (:start, :end)
|
|
67
|
+
# @param color [Symbol] Color variant of the chat bubble
|
|
68
|
+
# @param avatar [Hash] Options for the avatar component
|
|
69
|
+
# @param header [Hash] Options for the header metadata component
|
|
70
|
+
# @param footer [Hash] Options for the footer metadata component
|
|
71
|
+
def initialize(text = nil, position: :start, color: nil,
|
|
72
|
+
text_class: nil, header_class: nil,
|
|
73
|
+
footer_class: nil,
|
|
74
|
+
avatar: {},
|
|
75
|
+
header: {},
|
|
76
|
+
footer: {},
|
|
77
|
+
**system_arguments)
|
|
78
|
+
@text = text
|
|
79
|
+
@position = build_argument(position, POSITIONS, 'position')
|
|
80
|
+
@color = build_argument(color, COLORS, 'color')
|
|
81
|
+
@text_class = text_class
|
|
82
|
+
@header_class = header_class
|
|
83
|
+
@footer_class = footer_class
|
|
84
|
+
@avatar_options = avatar
|
|
85
|
+
@header_options = header
|
|
86
|
+
@footer_options = footer
|
|
87
|
+
@content_text = text
|
|
88
|
+
super(**system_arguments)
|
|
89
|
+
|
|
90
|
+
# Setup slots from provided options
|
|
91
|
+
with_avatar(**@avatar_options) if @avatar_options&.any?
|
|
92
|
+
with_header(**@header_options) if @header_options&.any?
|
|
93
|
+
with_footer(**@footer_options) if @footer_options&.any?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set the text content
|
|
97
|
+
def with_text(content = nil, &)
|
|
98
|
+
@content_text = if block_given?
|
|
99
|
+
capture(&)
|
|
100
|
+
else
|
|
101
|
+
content
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def call
|
|
106
|
+
tag.div(**html_attributes) do
|
|
107
|
+
components = []
|
|
108
|
+
components << avatar if avatar?
|
|
109
|
+
components << header if header?
|
|
110
|
+
components << render_bubble if @content_text.present?
|
|
111
|
+
components << footer if footer?
|
|
112
|
+
safe_join(components)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def render_bubble
|
|
119
|
+
tag.div(class: bubble_classes) do
|
|
120
|
+
helpers.sanitize(markdown_renderer.render(@content_text),
|
|
121
|
+
tags: %w[p br a ul ol li strong em del ins code pre blockquote h1 h2 h3 h4 h5 h6 img
|
|
122
|
+
span div],
|
|
123
|
+
attributes: %w[href target rel src alt class])
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def markdown_renderer
|
|
128
|
+
@markdown_renderer ||= Redcarpet::Markdown.new(
|
|
129
|
+
Redcarpet::Render::HTML.new(
|
|
130
|
+
hard_wrap: true,
|
|
131
|
+
link_attributes: { target: '_blank', rel: 'noopener' }
|
|
132
|
+
),
|
|
133
|
+
{
|
|
134
|
+
autolink: true,
|
|
135
|
+
strikethrough: true,
|
|
136
|
+
no_intra_emphasis: true,
|
|
137
|
+
underline: true,
|
|
138
|
+
highlight: true,
|
|
139
|
+
fenced_code_blocks: true,
|
|
140
|
+
disable_indented_code_blocks: true
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def bubble_classes
|
|
146
|
+
class_names(
|
|
147
|
+
'chat-bubble',
|
|
148
|
+
@color
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def computed_classes
|
|
153
|
+
class_names(
|
|
154
|
+
'chat',
|
|
155
|
+
@position,
|
|
156
|
+
system_arguments[:class]
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def html_attributes
|
|
161
|
+
system_arguments.merge(
|
|
162
|
+
class: computed_classes
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
class Item < BaseComponent
|
|
5
|
+
def initialize(href: nil, **system_arguments)
|
|
6
|
+
@href = href
|
|
7
|
+
super(**system_arguments)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
tag.li do
|
|
12
|
+
if @href
|
|
13
|
+
tag.a(href: @href, class: system_arguments[:class]) { content }
|
|
14
|
+
else
|
|
15
|
+
content
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
File without changes
|