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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.cursor/rules/how-to-write-tests.mdc +67 -0
  3. data/.cursor/rules/preview-file-stucture.mdc +95 -0
  4. data/.cursorrules +64 -0
  5. data/.github/dependabot.yml +12 -0
  6. data/.github/workflows/ci.yml +65 -0
  7. data/.gitignore +13 -0
  8. data/.vscode/launch.json +55 -0
  9. data/.vscode/settings.json +44 -0
  10. data/CHANGELOG.md +39 -0
  11. data/Gemfile +32 -0
  12. data/Gemfile.lock +335 -0
  13. data/MIT-LICENSE +20 -0
  14. data/README.md +64 -0
  15. data/Rakefile +7 -0
  16. data/app/assets/images/daisy_components/.keep +0 -0
  17. data/app/assets/stylesheets/daisy_ui/application.css +15 -0
  18. data/app/components/daisy_ui/actions/button.rb +262 -0
  19. data/app/components/daisy_ui/actions/dropdown.rb +125 -0
  20. data/app/components/daisy_ui/actions/swap.rb +126 -0
  21. data/app/components/daisy_ui/base_component.rb +29 -0
  22. data/app/components/daisy_ui/data_display/accordion.rb +128 -0
  23. data/app/components/daisy_ui/data_display/accordion_item.rb +121 -0
  24. data/app/components/daisy_ui/data_display/avatar.rb +131 -0
  25. data/app/components/daisy_ui/data_display/avatar_group.rb +102 -0
  26. data/app/components/daisy_ui/data_display/badge.rb +126 -0
  27. data/app/components/daisy_ui/data_display/card/actions.rb +94 -0
  28. data/app/components/daisy_ui/data_display/card/body.rb +113 -0
  29. data/app/components/daisy_ui/data_display/card/figure.rb +44 -0
  30. data/app/components/daisy_ui/data_display/card/title.rb +56 -0
  31. data/app/components/daisy_ui/data_display/card.rb +157 -0
  32. data/app/components/daisy_ui/data_display/chat.rb +92 -0
  33. data/app/components/daisy_ui/data_display/chat_bubble/metadata.rb +71 -0
  34. data/app/components/daisy_ui/data_display/chat_bubble.rb +166 -0
  35. data/app/components/daisy_ui/divider.rb +9 -0
  36. data/app/components/daisy_ui/item.rb +20 -0
  37. data/app/components/daisy_ui/title.rb +9 -0
  38. data/app/controllers/concerns/.keep +0 -0
  39. data/app/controllers/daisy_ui/application_controller.rb +6 -0
  40. data/app/helpers/daisy_ui/application_helper.rb +6 -0
  41. data/app/helpers/daisy_ui/icons_helper.rb +296 -0
  42. data/app/views/layouts/daisyui/application.html.erb +17 -0
  43. data/bin/parse_coverage.rb +59 -0
  44. data/bin/rails +57 -0
  45. data/bin/rubocop +10 -0
  46. data/bin/scrape_component +86 -0
  47. data/daisy_components.gemspec +33 -0
  48. data/docs/assets/2025-01_screeshot_1.png +0 -0
  49. data/docs/assets/2025-01_screeshot_2.png +0 -0
  50. data/docs/assets/2025-01_screeshot_3.png +0 -0
  51. data/lib/daisy_components.rb +5 -0
  52. data/lib/daisy_ui/engine.rb +51 -0
  53. data/lib/daisy_ui/version.rb +5 -0
  54. data/lib/daisy_ui.rb +13 -0
  55. data/lib/tasks/daisy_ui_tasks.rake +6 -0
  56. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ class Divider < ViewComponent::Base
5
+ def call
6
+ tag.li(class: 'divider')
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ class Title < ViewComponent::Base
5
+ def call
6
+ tag.li(class: 'menu-title') { content }
7
+ end
8
+ end
9
+ end
File without changes
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ class ApplicationController < ActionController::Base
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module ApplicationHelper
5
+ end
6
+ end