senren-ui 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/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +63 -0
- data/LICENSE +21 -0
- data/README.md +135 -0
- data/Rakefile +22 -0
- data/docs/visual_style.md +51 -0
- data/lib/generators/senren/component/component_generator.rb +62 -0
- data/lib/generators/senren/component/templates/component.html.erb.tt +3 -0
- data/lib/generators/senren/component/templates/component.rb.tt +13 -0
- data/lib/generators/senren/component/templates/component_test.rb.tt +16 -0
- data/lib/generators/senren/component/templates/controller.js.tt +23 -0
- data/lib/generators/senren/component/templates/system_test.rb.tt +7 -0
- data/lib/generators/senren/install/install_generator.rb +67 -0
- data/lib/generators/senren/install/templates/base_component.rb.tt +45 -0
- data/lib/generators/senren/install/templates/conventions.md.tt +66 -0
- data/lib/generators/senren/install/templates/installed_components.yml.tt +4 -0
- data/lib/generators/senren/install/templates/senren.css.tt +164 -0
- data/lib/senren/rails/component_copier.rb +111 -0
- data/lib/senren/rails/doctor.rb +86 -0
- data/lib/senren/rails/engine.rb +16 -0
- data/lib/senren/rails/host_paths.rb +36 -0
- data/lib/senren/rails/installer.rb +83 -0
- data/lib/senren/rails/llms_writer.rb +149 -0
- data/lib/senren/rails/registry.rb +161 -0
- data/lib/senren/rails/skill_writer.rb +166 -0
- data/lib/senren/rails/version.rb +7 -0
- data/lib/senren/rails.rb +39 -0
- data/lib/tasks/senren.rake +74 -0
- data/registry/components.yml +1053 -0
- data/registry/groups.yml +25 -0
- data/registry/recipes.yml +79 -0
- data/templates/components/accordion/accordion_component.html.erb +16 -0
- data/templates/components/accordion/accordion_component.rb +31 -0
- data/templates/components/activity_feed/activity_feed_component.html.erb +22 -0
- data/templates/components/activity_feed/activity_feed_component.rb +19 -0
- data/templates/components/alert/alert_component.html.erb +9 -0
- data/templates/components/alert/alert_component.rb +18 -0
- data/templates/components/alert_dialog/alert_dialog_component.html.erb +34 -0
- data/templates/components/alert_dialog/alert_dialog_component.rb +21 -0
- data/templates/components/api_key_field/api_key_field_component.html.erb +13 -0
- data/templates/components/api_key_field/api_key_field_component.rb +20 -0
- data/templates/components/app_shell/app_shell_component.html.erb +28 -0
- data/templates/components/app_shell/app_shell_component.rb +24 -0
- data/templates/components/aspect_ratio/aspect_ratio_component.html.erb +3 -0
- data/templates/components/aspect_ratio/aspect_ratio_component.rb +14 -0
- data/templates/components/avatar/avatar_component.html.erb +27 -0
- data/templates/components/avatar/avatar_component.rb +30 -0
- data/templates/components/badge/badge_component.html.erb +1 -0
- data/templates/components/badge/badge_component.rb +16 -0
- data/templates/components/billing_plan_card/billing_plan_card_component.html.erb +28 -0
- data/templates/components/billing_plan_card/billing_plan_card_component.rb +27 -0
- data/templates/components/breadcrumb/breadcrumb_component.html.erb +23 -0
- data/templates/components/breadcrumb/breadcrumb_component.rb +30 -0
- data/templates/components/bulk_action_bar/bulk_action_bar_component.html.erb +12 -0
- data/templates/components/bulk_action_bar/bulk_action_bar_component.rb +24 -0
- data/templates/components/button/button_component.html.erb +6 -0
- data/templates/components/button/button_component.rb +29 -0
- data/templates/components/calendar/calendar_component.html.erb +21 -0
- data/templates/components/calendar/calendar_component.rb +30 -0
- data/templates/components/card/card_component.html.erb +13 -0
- data/templates/components/card/card_component.rb +17 -0
- data/templates/components/carousel/carousel_component.html.erb +68 -0
- data/templates/components/carousel/carousel_component.rb +34 -0
- data/templates/components/checkbox/checkbox_component.html.erb +8 -0
- data/templates/components/checkbox/checkbox_component.rb +19 -0
- data/templates/components/checkbox_group/checkbox_group_component.html.erb +10 -0
- data/templates/components/checkbox_group/checkbox_group_component.rb +30 -0
- data/templates/components/clipboard/clipboard_component.html.erb +7 -0
- data/templates/components/clipboard/clipboard_component.rb +17 -0
- data/templates/components/codeblock/codeblock_component.html.erb +11 -0
- data/templates/components/codeblock/codeblock_component.rb +31 -0
- data/templates/components/collapsible/collapsible_component.html.erb +9 -0
- data/templates/components/collapsible/collapsible_component.rb +19 -0
- data/templates/components/combobox/combobox_component.html.erb +19 -0
- data/templates/components/combobox/combobox_component.rb +38 -0
- data/templates/components/command/command_component.html.erb +22 -0
- data/templates/components/command/command_component.rb +38 -0
- data/templates/components/context_menu/context_menu_component.html.erb +11 -0
- data/templates/components/context_menu/context_menu_component.rb +11 -0
- data/templates/components/data_table/data_table_component.html.erb +50 -0
- data/templates/components/data_table/data_table_component.rb +42 -0
- data/templates/components/date_picker/date_picker_component.html.erb +5 -0
- data/templates/components/date_picker/date_picker_component.rb +21 -0
- data/templates/components/dialog/dialog_component.html.erb +38 -0
- data/templates/components/dialog/dialog_component.rb +22 -0
- data/templates/components/dropdown_menu/dropdown_menu_component.html.erb +12 -0
- data/templates/components/dropdown_menu/dropdown_menu_component.rb +36 -0
- data/templates/components/empty_state/empty_state_component.html.erb +18 -0
- data/templates/components/empty_state/empty_state_component.rb +22 -0
- data/templates/components/filter_bar/filter_bar_component.html.erb +5 -0
- data/templates/components/filter_bar/filter_bar_component.rb +15 -0
- data/templates/components/form/form_component.html.erb +3 -0
- data/templates/components/form/form_component.rb +18 -0
- data/templates/components/hover_card/hover_card_component.html.erb +10 -0
- data/templates/components/hover_card/hover_card_component.rb +11 -0
- data/templates/components/input/input_component.html.erb +1 -0
- data/templates/components/input/input_component.rb +28 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.html.erb +35 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +26 -0
- data/templates/components/label/label_component.html.erb +4 -0
- data/templates/components/label/label_component.rb +19 -0
- data/templates/components/link/link_component.html.erb +1 -0
- data/templates/components/link/link_component.rb +25 -0
- data/templates/components/masked_input/masked_input_component.html.erb +1 -0
- data/templates/components/masked_input/masked_input_component.rb +18 -0
- data/templates/components/native_select/native_select_component.html.erb +14 -0
- data/templates/components/native_select/native_select_component.rb +52 -0
- data/templates/components/page_header/page_header_component.html.erb +20 -0
- data/templates/components/page_header/page_header_component.rb +19 -0
- data/templates/components/pagination/pagination_component.html.erb +11 -0
- data/templates/components/pagination/pagination_component.rb +24 -0
- data/templates/components/popover/popover_component.html.erb +9 -0
- data/templates/components/popover/popover_component.rb +11 -0
- data/templates/components/progress/progress_component.html.erb +11 -0
- data/templates/components/progress/progress_component.rb +26 -0
- data/templates/components/radio_button/radio_button_component.html.erb +8 -0
- data/templates/components/radio_button/radio_button_component.rb +19 -0
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +32 -0
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +30 -0
- data/templates/components/search_input/search_input_component.html.erb +14 -0
- data/templates/components/search_input/search_input_component.rb +18 -0
- data/templates/components/select/select_component.html.erb +1 -0
- data/templates/components/select/select_component.rb +19 -0
- data/templates/components/separator/separator_component.html.erb +1 -0
- data/templates/components/separator/separator_component.rb +12 -0
- data/templates/components/settings_section/settings_section_component.html.erb +20 -0
- data/templates/components/settings_section/settings_section_component.rb +18 -0
- data/templates/components/sheet/sheet_component.html.erb +37 -0
- data/templates/components/sheet/sheet_component.rb +27 -0
- data/templates/components/shortcut_key/shortcut_key_component.html.erb +6 -0
- data/templates/components/shortcut_key/shortcut_key_component.rb +15 -0
- data/templates/components/sidebar/sidebar_component.html.erb +14 -0
- data/templates/components/sidebar/sidebar_component.rb +37 -0
- data/templates/components/skeleton/skeleton_component.html.erb +1 -0
- data/templates/components/skeleton/skeleton_component.rb +13 -0
- data/templates/components/stat_card/stat_card_component.html.erb +20 -0
- data/templates/components/stat_card/stat_card_component.rb +31 -0
- data/templates/components/switch/switch_component.html.erb +11 -0
- data/templates/components/switch/switch_component.rb +19 -0
- data/templates/components/table/table_component.html.erb +26 -0
- data/templates/components/table/table_component.rb +35 -0
- data/templates/components/tabs/tabs_component.html.erb +18 -0
- data/templates/components/tabs/tabs_component.rb +35 -0
- data/templates/components/team_member_list/team_member_list_component.html.erb +22 -0
- data/templates/components/team_member_list/team_member_list_component.rb +26 -0
- data/templates/components/textarea/textarea_component.html.erb +1 -0
- data/templates/components/textarea/textarea_component.rb +23 -0
- data/templates/components/theme_toggle/theme_toggle_component.html.erb +4 -0
- data/templates/components/theme_toggle/theme_toggle_component.rb +15 -0
- data/templates/components/tooltip/tooltip_component.html.erb +9 -0
- data/templates/components/tooltip/tooltip_component.rb +16 -0
- data/templates/components/top_nav/top_nav_component.html.erb +21 -0
- data/templates/components/top_nav/top_nav_component.rb +44 -0
- data/templates/components/typography/typography_component.html.erb +1 -0
- data/templates/components/typography/typography_component.rb +24 -0
- data/templates/controllers/accordion_controller.js +27 -0
- data/templates/controllers/alert_dialog_controller.js +38 -0
- data/templates/controllers/api_key_field_controller.js +36 -0
- data/templates/controllers/calendar_controller.js +16 -0
- data/templates/controllers/carousel_controller.js +50 -0
- data/templates/controllers/clipboard_controller.js +17 -0
- data/templates/controllers/collapsible_controller.js +13 -0
- data/templates/controllers/combobox_controller.js +64 -0
- data/templates/controllers/command_controller.js +80 -0
- data/templates/controllers/context_menu_controller.js +36 -0
- data/templates/controllers/data_table_controller.js +34 -0
- data/templates/controllers/date_picker_controller.js +17 -0
- data/templates/controllers/dialog_controller.js +50 -0
- data/templates/controllers/dropdown_menu_controller.js +92 -0
- data/templates/controllers/hover_card_controller.js +17 -0
- data/templates/controllers/invite_member_dialog_controller.js +28 -0
- data/templates/controllers/masked_input_controller.js +30 -0
- data/templates/controllers/popover_controller.js +42 -0
- data/templates/controllers/rich_text_editor_lite_controller.js +443 -0
- data/templates/controllers/select_controller.js +10 -0
- data/templates/controllers/sheet_controller.js +34 -0
- data/templates/controllers/sidebar_controller.js +10 -0
- data/templates/controllers/tabs_controller.js +41 -0
- data/templates/controllers/theme_toggle_controller.js +24 -0
- data/templates/controllers/tooltip_controller.js +10 -0
- metadata +257 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Senren
|
|
2
|
+
class DataTableComponent < BaseComponent
|
|
3
|
+
renders_one :toolbar
|
|
4
|
+
renders_one :footer
|
|
5
|
+
|
|
6
|
+
VARIANTS = { default: '' }.freeze
|
|
7
|
+
SIZES = { md: '' }.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(columns: [], rows: [], caption: nil, empty_text: 'No records found.', sortable: true,
|
|
10
|
+
variant: :default, class_name: nil, **html)
|
|
11
|
+
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
12
|
+
@columns = Array(columns)
|
|
13
|
+
@rows = Array(rows)
|
|
14
|
+
@caption = caption
|
|
15
|
+
@empty_text = empty_text
|
|
16
|
+
@sortable = sortable
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :columns, :rows, :caption, :empty_text
|
|
20
|
+
|
|
21
|
+
def sortable? = !!@sortable
|
|
22
|
+
|
|
23
|
+
def cell_value(row, column)
|
|
24
|
+
key = column_key(column)
|
|
25
|
+
row.is_a?(Hash) ? (row[key] || row[key.to_s]) : row[key.to_i]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def column_label(column)
|
|
29
|
+
column.is_a?(Hash) ? (column[:label] || column['label']) : column.to_s.tr('_', ' ').capitalize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def column_sort_key(column)
|
|
33
|
+
column_key(column).to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def column_key(column)
|
|
39
|
+
column.is_a?(Hash) ? (column[:key] || column['key']) : column
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<div <%= tag.attributes(**root_attrs("flex w-full items-center gap-2", data: { controller: "senren--date-picker" })) %>>
|
|
2
|
+
<input type="date" id="<%= id %>" name="<%= name %>" value="<%= value %>" placeholder="<%= placeholder %>" data-senren--date-picker-target="input" class="hover:cursor-pointerh-10 w-full rounded-(--senren-radius) border bg-[hsl(var(--senren-background))] px-3 text-sm text-[hsl(var(--senren-foreground))] outline-none transition-colors focus:ring-2 focus:ring-[hsl(var(--senren-ring))] <%= self.class::VARIANTS[variant] %>">
|
|
3
|
+
<button type="button" data-action="click->senren--date-picker#today" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) border border-[hsl(var(--senren-border))] px-3 text-sm font-medium hover:bg-[hsl(var(--senren-accent))]">Today</button>
|
|
4
|
+
<button type="button" data-action="click->senren--date-picker#clear" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) px-3 text-sm text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))]">Clear</button>
|
|
5
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class DatePickerComponent < BaseComponent
|
|
5
|
+
VARIANTS = {
|
|
6
|
+
default: 'border-[hsl(var(--senren-input))]',
|
|
7
|
+
error: 'border-[hsl(var(--senren-destructive))]'
|
|
8
|
+
}.freeze
|
|
9
|
+
SIZES = { md: '' }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(name:, value: nil, id: nil, placeholder: 'Select date', variant: :default, class_name: nil, **html)
|
|
12
|
+
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
13
|
+
@name = name
|
|
14
|
+
@value = value
|
|
15
|
+
@id = id || name
|
|
16
|
+
@placeholder = placeholder
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :name, :value, :id, :placeholder
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div data-controller="senren--dialog" data-senren-component="dialog" data-senren--dialog-open-value="<%= open %>" id="<%= dom_id %>">
|
|
2
|
+
<% if trigger? %>
|
|
3
|
+
<span data-action="click->senren--dialog#open" data-senren--dialog-target="trigger" aria-controls="<%= dom_id %>-panel">
|
|
4
|
+
<%= trigger %>
|
|
5
|
+
</span>
|
|
6
|
+
<% else %>
|
|
7
|
+
<button type="button" data-action="click->senren--dialog#open"
|
|
8
|
+
class="hidden" data-senren--dialog-target="trigger" aria-controls="<%= dom_id %>-panel">Open</button>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<div data-senren--dialog-target="overlay" hidden
|
|
12
|
+
class="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm"></div>
|
|
13
|
+
|
|
14
|
+
<div data-senren--dialog-target="panel" hidden
|
|
15
|
+
role="dialog" aria-modal="true" aria-labelledby="<%= dom_id %>-title"
|
|
16
|
+
id="<%= dom_id %>-panel"
|
|
17
|
+
tabindex="-1"
|
|
18
|
+
class="fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-6 shadow-lg focus:outline-none">
|
|
19
|
+
<% if title? %>
|
|
20
|
+
<h2 id="<%= dom_id %>-title" class="text-lg font-semibold leading-none tracking-tight"><%= title %></h2>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% if description? %>
|
|
23
|
+
<p class="mt-1.5 text-sm text-[hsl(var(--senren-muted-foreground))]"><%= description %></p>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% if body? %>
|
|
26
|
+
<div class="mt-4"><%= body %></div>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% if footer? %>
|
|
29
|
+
<div class="mt-6 flex justify-end gap-2"><%= footer %></div>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
<button type="button" data-action="click->senren--dialog#close"
|
|
33
|
+
aria-label="Close"
|
|
34
|
+
class="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
35
|
+
<span aria-hidden="true">×</span>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class DialogComponent < BaseComponent
|
|
5
|
+
renders_one :trigger
|
|
6
|
+
renders_one :title
|
|
7
|
+
renders_one :description
|
|
8
|
+
renders_one :body
|
|
9
|
+
renders_one :footer
|
|
10
|
+
|
|
11
|
+
VARIANTS = { default: '' }.freeze
|
|
12
|
+
SIZES = { md: '' }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(open: false, id: nil, class_name: nil, **html)
|
|
15
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
16
|
+
@open = open
|
|
17
|
+
@dom_id = id || "senren-dialog-#{SecureRandom.hex(3)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :open, :dom_id
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div data-controller="senren--dropdown-menu" class="relative inline-block" data-state="closed" data-senren-component="dropdown_menu">
|
|
2
|
+
<div class="inline-flex cursor-pointer" data-state="closed" aria-haspopup="menu" aria-expanded="false" data-senren--dropdown-menu-target="trigger" data-action="click->senren--dropdown-menu#toggle keydown->senren--dropdown-menu#onTriggerKey">
|
|
3
|
+
<%= trigger %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div data-senren--dropdown-menu-target="menu" role="menu" hidden
|
|
7
|
+
class="absolute right-0 z-50 mt-2 w-56 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-1 shadow-md">
|
|
8
|
+
<% items.each do |it| %>
|
|
9
|
+
<%= it %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class DropdownMenuComponent < BaseComponent
|
|
5
|
+
renders_one :trigger
|
|
6
|
+
renders_many :items, 'ItemTag'
|
|
7
|
+
|
|
8
|
+
VARIANTS = { default: '' }.freeze
|
|
9
|
+
SIZES = { md: '' }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(class_name: nil, **html)
|
|
12
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ItemTag < ViewComponent::Base
|
|
16
|
+
def initialize(href: nil, method: nil, destructive: false, **opts)
|
|
17
|
+
@href = href
|
|
18
|
+
@method = method
|
|
19
|
+
@destructive = destructive
|
|
20
|
+
@opts = opts
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
klass = 'block w-full text-left px-3 py-2 text-sm rounded-sm hover:bg-[hsl(var(--senren-accent))] focus:bg-[hsl(var(--senren-accent))] outline-none cursor-pointer'
|
|
25
|
+
klass += ' text-[hsl(var(--senren-destructive))]' if @destructive
|
|
26
|
+
if @href
|
|
27
|
+
link_to(content, @href, role: 'menuitem', method: @method, class: klass,
|
|
28
|
+
data: { action: 'click->senren--dropdown-menu#close keydown->senren--dropdown-menu#onItemKey' })
|
|
29
|
+
else
|
|
30
|
+
tag.button(content, type: 'button', role: 'menuitem', class: klass,
|
|
31
|
+
data: { action: 'click->senren--dropdown-menu#close keydown->senren--dropdown-menu#onItemKey' }, **@opts)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<%= tag.div(**root_attrs("rounded-(--senren-radius) border border-dashed p-8 text-center text-[hsl(var(--senren-muted-foreground))]")) do %>
|
|
2
|
+
<% if content? && title.blank? && description.blank? %>
|
|
3
|
+
<%= content %>
|
|
4
|
+
<% else %>
|
|
5
|
+
<div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-[hsl(var(--senren-accent))] text-[hsl(var(--senren-accent-foreground))]">
|
|
6
|
+
<% if icon? %><%= icon %><% else %><span class="text-lg font-semibold">+</span><% end %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if title.present? %>
|
|
9
|
+
<h2 class="mt-4 font-display text-lg font-semibold text-[hsl(var(--senren-foreground))]"><%= title %></h2>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if description.present? %>
|
|
12
|
+
<p class="mx-auto mt-2 max-w-sm text-sm leading-6"><%= description %></p>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% if actions? %>
|
|
15
|
+
<div class="mt-5 flex justify-center gap-2"><%= actions %></div>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class EmptyStateComponent < BaseComponent
|
|
5
|
+
renders_one :icon
|
|
6
|
+
renders_one :actions
|
|
7
|
+
|
|
8
|
+
VARIANTS = {
|
|
9
|
+
default: 'border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))]',
|
|
10
|
+
illustrated: 'border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-muted)/0.35)]'
|
|
11
|
+
}.freeze
|
|
12
|
+
SIZES = { md: '' }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(title: nil, description: nil, variant: :default, class_name: nil, **html)
|
|
15
|
+
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
16
|
+
@title = title
|
|
17
|
+
@description = description
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :title, :description
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class FilterBarComponent < BaseComponent
|
|
5
|
+
VARIANTS = { default: '' }.freeze
|
|
6
|
+
SIZES = { md: '' }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(label: 'Filters', class_name: nil, **html)
|
|
9
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
|
+
@label = label
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :label
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class FormComponent < BaseComponent
|
|
5
|
+
VARIANTS = { default: '' }.freeze
|
|
6
|
+
SIZES = { md: '' }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(model: nil, url: nil, method: :post, multipart: false, class_name: nil, **html)
|
|
9
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
|
+
@model = model
|
|
11
|
+
@url = url
|
|
12
|
+
@method = method
|
|
13
|
+
@multipart = multipart
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :model, :url, :method, :multipart
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<span data-controller="senren--hover-card" class="relative inline-block" data-senren-component="hover_card">
|
|
2
|
+
<span tabindex="0"
|
|
3
|
+
data-action="mouseenter->senren--hover-card#show mouseleave->senren--hover-card#hide focus->senren--hover-card#show blur->senren--hover-card#hide">
|
|
4
|
+
<%= trigger %>
|
|
5
|
+
</span>
|
|
6
|
+
<span hidden data-senren--hover-card-target="panel"
|
|
7
|
+
class="absolute z-40 mt-2 w-64 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-4 shadow-md">
|
|
8
|
+
<%= content_panel || content %>
|
|
9
|
+
</span>
|
|
10
|
+
</span>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.input(**root_attrs("flex w-full rounded-(--senren-radius) border bg-[hsl(var(--senren-background))] text-[hsl(var(--senren-foreground))] file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[hsl(var(--senren-muted-foreground))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50", id: id, name: name, type: type, value: value, placeholder: placeholder, "aria-invalid": variant == :error)) %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class InputComponent < BaseComponent
|
|
5
|
+
VARIANTS = {
|
|
6
|
+
default: 'border-[hsl(var(--senren-input))] focus-visible:ring-[hsl(var(--senren-ring))]',
|
|
7
|
+
error: 'border-[hsl(var(--senren-destructive))] focus-visible:ring-[hsl(var(--senren-destructive))]'
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
SIZES = {
|
|
11
|
+
sm: 'h-8 text-sm px-2.5',
|
|
12
|
+
md: 'h-10 text-sm px-3',
|
|
13
|
+
lg: 'h-12 text-base px-4'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(name:, type: 'text', value: nil, placeholder: nil, id: nil, variant: :default, size: :md,
|
|
17
|
+
class_name: nil, **html)
|
|
18
|
+
super(variant: variant, size: size, class_name: class_name, **html)
|
|
19
|
+
@name = name
|
|
20
|
+
@type = type
|
|
21
|
+
@value = value
|
|
22
|
+
@placeholder = placeholder
|
|
23
|
+
@id = id || name.to_s.parameterize
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :name, :type, :value, :placeholder, :id
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<%= tag.div(**root_attrs("inline-block", data: { controller: "senren--invite-member-dialog" })) do %>
|
|
2
|
+
<% if trigger? %>
|
|
3
|
+
<span data-action="click->senren--invite-member-dialog#open" data-senren--invite-member-dialog-target="trigger"><%= trigger %></span>
|
|
4
|
+
<% else %>
|
|
5
|
+
<button type="button" class="inline-flex h-10 cursor-pointer items-center justify-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-4 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90" data-action="click->senren--invite-member-dialog#open" data-senren--invite-member-dialog-target="trigger">
|
|
6
|
+
<%= button_label %>
|
|
7
|
+
</button>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<div hidden data-senren--invite-member-dialog-target="overlay" class="fixed inset-0 z-40 bg-[hsl(var(--senren-foreground)/0.42)] backdrop-blur-sm"></div>
|
|
11
|
+
<div hidden id="<%= dom_id %>" data-senren--invite-member-dialog-target="panel" role="dialog" aria-modal="true" aria-labelledby="<%= dom_id %>-title" tabindex="-1" class="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] p-6 text-[hsl(var(--senren-popover-foreground))] shadow-lg">
|
|
12
|
+
<h2 id="<%= dom_id %>-title" class="font-display text-lg font-semibold"><%= title %></h2>
|
|
13
|
+
<% if description.present? %>
|
|
14
|
+
<p class="mt-1.5 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]"><%= description %></p>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<div class="mt-5 space-y-4">
|
|
18
|
+
<% if content? %>
|
|
19
|
+
<%= content %>
|
|
20
|
+
<% else %>
|
|
21
|
+
<label class="block text-sm font-medium text-[hsl(var(--senren-foreground))]" for="<%= dom_id %>-email">Email</label>
|
|
22
|
+
<input id="<%= dom_id %>-email" name="<%= email_name %>" type="email" placeholder="teammate@company.com" class="h-10 w-full rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-3 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
23
|
+
<label class="block text-sm font-medium text-[hsl(var(--senren-foreground))]" for="<%= dom_id %>-role">Role</label>
|
|
24
|
+
<select id="<%= dom_id %>-role" name="<%= role_name %>" class="h-10 w-full rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-3 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
|
|
25
|
+
<% roles.each do |role| %><option><%= role %></option><% end %>
|
|
26
|
+
</select>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="mt-6 flex justify-end gap-2">
|
|
31
|
+
<button type="button" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) px-4 text-sm font-medium hover:bg-[hsl(var(--senren-accent))]" data-action="click->senren--invite-member-dialog#close">Cancel</button>
|
|
32
|
+
<% if footer? %><%= footer %><% else %><button type="submit" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-4 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90">Send invite</button><% end %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class InviteMemberDialogComponent < BaseComponent
|
|
5
|
+
renders_one :trigger
|
|
6
|
+
renders_one :footer
|
|
7
|
+
|
|
8
|
+
VARIANTS = { default: '' }.freeze
|
|
9
|
+
SIZES = { md: '' }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(title: 'Invite teammate', description: 'Send an invitation to join this workspace.',
|
|
12
|
+
email_name: 'email', role_name: 'role', roles: %w[Member
|
|
13
|
+
Admin], button_label: 'Invite member', id: nil, class_name: nil, **html)
|
|
14
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
15
|
+
@title = title
|
|
16
|
+
@description = description
|
|
17
|
+
@email_name = email_name
|
|
18
|
+
@role_name = role_name
|
|
19
|
+
@roles = Array(roles)
|
|
20
|
+
@button_label = button_label
|
|
21
|
+
@dom_id = id || "senren-invite-member-#{SecureRandom.hex(3)}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :title, :description, :email_name, :role_name, :roles, :button_label, :dom_id
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<%= tag.label(**root_attrs("text-sm font-medium leading-none text-[hsl(var(--senren-foreground))] peer-disabled:cursor-not-allowed peer-disabled:opacity-70", for: for_field)) do %>
|
|
2
|
+
<%= content %>
|
|
3
|
+
<% if variant == :required %><span class="text-[hsl(var(--senren-destructive))]" aria-hidden="true"> *</span><% end %>
|
|
4
|
+
<% end %>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class LabelComponent < BaseComponent
|
|
5
|
+
VARIANTS = {
|
|
6
|
+
default: '',
|
|
7
|
+
required: ''
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
SIZES = { md: '' }.freeze
|
|
11
|
+
|
|
12
|
+
def initialize(for_field:, variant: :default, class_name: nil, **html)
|
|
13
|
+
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
14
|
+
@for_field = for_field
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :for_field
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= tag.a content, **root_attrs("inline-flex items-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))] rounded-sm", href: href, **external_attrs) %>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class LinkComponent < BaseComponent
|
|
5
|
+
VARIANTS = {
|
|
6
|
+
default: 'text-[hsl(var(--senren-primary))] hover:underline underline-offset-4',
|
|
7
|
+
muted: 'text-[hsl(var(--senren-muted-foreground))] hover:underline underline-offset-4',
|
|
8
|
+
destructive: 'text-[hsl(var(--senren-destructive))] hover:underline underline-offset-4'
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
SIZES = { md: '' }.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(href:, variant: :default, external: false, class_name: nil, **html)
|
|
14
|
+
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
15
|
+
@href = href
|
|
16
|
+
@external = external
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :href, :external
|
|
20
|
+
|
|
21
|
+
def external_attrs
|
|
22
|
+
external ? { rel: 'noopener noreferrer', target: '_blank' } : {}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Senren::InputComponent.new(**input_args)) { content } %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class MaskedInputComponent < BaseComponent
|
|
5
|
+
VARIANTS = InputComponent::VARIANTS
|
|
6
|
+
SIZES = InputComponent::SIZES
|
|
7
|
+
|
|
8
|
+
def initialize(mask:, **args)
|
|
9
|
+
@mask = mask
|
|
10
|
+
@args = args
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def input_args
|
|
14
|
+
data = (@args[:data] || {}).merge(controller: 'senren--masked-input', 'senren--masked-input-mask-value': @mask)
|
|
15
|
+
@args.merge(data: data)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%= tag.div(**wrapper_attrs) do %>
|
|
2
|
+
<%= tag.select(**select_attrs) do %>
|
|
3
|
+
<% if prompt %>
|
|
4
|
+
<option value=""><%= prompt %></option>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% options.each do |opt| %>
|
|
7
|
+
<% value, label = Array === opt ? opt : [opt, opt] %>
|
|
8
|
+
<option value="<%= value %>" <%= "selected" if selected.to_s == value.to_s %>><%= label %></option>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[hsl(var(--senren-muted-foreground))] transition-transform duration-150 group-focus-within:rotate-180">
|
|
12
|
+
<path d="m5 8 5 5 5-5"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class NativeSelectComponent < BaseComponent
|
|
5
|
+
VARIANTS = {
|
|
6
|
+
default: 'border-[hsl(var(--senren-input))] focus-visible:ring-[hsl(var(--senren-ring))]',
|
|
7
|
+
error: 'border-[hsl(var(--senren-destructive))] focus-visible:ring-[hsl(var(--senren-destructive))]'
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
SIZES = {
|
|
11
|
+
sm: 'h-8 text-sm px-2.5',
|
|
12
|
+
md: 'h-10 text-sm px-3',
|
|
13
|
+
lg: 'h-12 text-base px-4'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(name:, options:, selected: nil, id: nil, prompt: nil, variant: :default, size: :md, class_name: nil,
|
|
17
|
+
**html)
|
|
18
|
+
super(variant: variant, size: size, class_name: class_name, **html)
|
|
19
|
+
@name = name
|
|
20
|
+
@options = options
|
|
21
|
+
@selected = selected
|
|
22
|
+
@id = id || name.to_s.parameterize
|
|
23
|
+
@prompt = prompt
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :name, :options, :selected, :id, :prompt
|
|
27
|
+
|
|
28
|
+
def wrapper_attrs
|
|
29
|
+
{ class: 'group relative w-full', data: { senren_component: senren_component_name } }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def select_attrs
|
|
33
|
+
attrs = html_attrs.except(:class)
|
|
34
|
+
attrs.merge(
|
|
35
|
+
id: id,
|
|
36
|
+
name: name,
|
|
37
|
+
class: select_classes,
|
|
38
|
+
'aria-invalid': variant == :error
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def select_classes
|
|
43
|
+
[
|
|
44
|
+
'flex w-full cursor-pointer appearance-none rounded-(--senren-radius) border bg-[hsl(var(--senren-background))] pr-9 text-[hsl(var(--senren-foreground))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50',
|
|
45
|
+
self.class::VARIANTS[variant],
|
|
46
|
+
self.class::SIZES[size],
|
|
47
|
+
class_name,
|
|
48
|
+
html_attrs[:class]
|
|
49
|
+
].compact.join(' ')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%= tag.header(**root_attrs("flex flex-col gap-4 border-b border-[hsl(var(--senren-border))] pb-6 sm:flex-row sm:items-end sm:justify-between")) do %>
|
|
2
|
+
<% if content? && title.blank? && description.blank? %>
|
|
3
|
+
<%= content %>
|
|
4
|
+
<% else %>
|
|
5
|
+
<div class="min-w-0">
|
|
6
|
+
<% if eyebrow? %>
|
|
7
|
+
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-[hsl(var(--senren-muted-foreground))]"><%= eyebrow %></div>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if title.present? %>
|
|
10
|
+
<h1 class="font-display text-3xl font-semibold tracking-tight text-[hsl(var(--senren-foreground))]"><%= title %></h1>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% if description.present? %>
|
|
13
|
+
<p class="mt-2 max-w-2xl text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]"><%= description %></p>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
<% if actions? %>
|
|
17
|
+
<div class="flex shrink-0 flex-wrap items-center gap-2"><%= actions %></div>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class PageHeaderComponent < BaseComponent
|
|
5
|
+
renders_one :actions
|
|
6
|
+
renders_one :eyebrow
|
|
7
|
+
|
|
8
|
+
VARIANTS = { default: '' }.freeze
|
|
9
|
+
SIZES = { md: '' }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(title: nil, description: nil, class_name: nil, **html)
|
|
12
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
13
|
+
@title = title
|
|
14
|
+
@description = description
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :title, :description
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<nav <%= tag.attributes(**root_attrs("flex items-center justify-center gap-1", aria: { label: label })) %>>
|
|
2
|
+
<% prev_page = current_page - 1 %>
|
|
3
|
+
<%= link_to "Previous", page_url(prev_page), class: "rounded-md border border-[hsl(var(--senren-border))] px-3 py-2 text-sm #{current_page == 1 ? "pointer-events-none opacity-50" : "hover:bg-[hsl(var(--senren-accent))]"}", aria: { disabled: current_page == 1 } %>
|
|
4
|
+
|
|
5
|
+
<% (1..total_pages).each do |page| %>
|
|
6
|
+
<%= link_to page, page_url(page), class: "inline-flex h-9 min-w-9 items-center justify-center rounded-md px-3 text-sm font-medium #{page == current_page ? "bg-[hsl(var(--senren-primary))] text-[hsl(var(--senren-primary-foreground))]" : "text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-accent-foreground))]"}", aria: { current: (page == current_page ? "page" : nil) } %>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
<% next_page = current_page + 1 %>
|
|
10
|
+
<%= link_to "Next", page_url(next_page), class: "rounded-md border border-[hsl(var(--senren-border))] px-3 py-2 text-sm #{current_page == total_pages ? "pointer-events-none opacity-50" : "hover:bg-[hsl(var(--senren-accent))]"}", aria: { disabled: current_page == total_pages } %>
|
|
11
|
+
</nav>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senren
|
|
4
|
+
class PaginationComponent < BaseComponent
|
|
5
|
+
VARIANTS = { default: '' }.freeze
|
|
6
|
+
SIZES = { md: '' }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(current_page: 1, total_pages: 1, path: nil, label: 'Pagination', class_name: nil, **html)
|
|
9
|
+
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
|
+
@total_pages = [total_pages.to_i, 1].max
|
|
11
|
+
@current_page = current_page.to_i.clamp(1, @total_pages)
|
|
12
|
+
@path = path
|
|
13
|
+
@label = label
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :current_page, :total_pages, :path, :label
|
|
17
|
+
|
|
18
|
+
def page_url(page)
|
|
19
|
+
return '#' unless path
|
|
20
|
+
|
|
21
|
+
path.respond_to?(:call) ? path.call(page) : path.to_s.gsub(':page', page.to_s)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div data-controller="senren--popover" class="relative inline-block" data-senren-component="popover">
|
|
2
|
+
<div data-senren--popover-target="trigger" data-action="click->senren--popover#toggle">
|
|
3
|
+
<%= trigger %>
|
|
4
|
+
</div>
|
|
5
|
+
<div data-senren--popover-target="panel" hidden role="dialog"
|
|
6
|
+
class="absolute z-40 mt-2 min-w-[12rem] rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-4 shadow-md">
|
|
7
|
+
<%= content_panel || content %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|