senren-ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -0
- data/CONTRIBUTING.md +10 -0
- data/README.md +36 -0
- data/docs/i18n.md +194 -0
- data/lib/generators/senren/install/install_generator.rb +36 -0
- data/lib/generators/senren/install/templates/base_component.rb.tt +39 -0
- data/lib/generators/senren/install/templates/conventions.md.tt +16 -0
- data/lib/senren/rails/agent_rules_writer.rb +4 -0
- data/lib/senren/rails/base_component_patch.rb +24 -0
- data/lib/senren/rails/component_copier.rb +21 -0
- data/lib/senren/rails/host_paths.rb +5 -0
- data/lib/senren/rails/locale_installer.rb +90 -0
- data/lib/senren/rails/version.rb +1 -1
- data/lib/senren/rails.rb +1 -0
- data/lib/tasks/senren.rake +9 -0
- data/templates/components/accordion/accordion_component.html.erb +22 -4
- data/templates/components/accordion/accordion_component.rb +2 -1
- data/templates/components/alert_dialog/alert_dialog_component.html.erb +1 -1
- data/templates/components/api_key_field/api_key_field_component.html.erb +1 -1
- data/templates/components/api_key_field/api_key_field_component.rb +8 -1
- data/templates/components/app_shell/app_shell_component.rb +2 -1
- data/templates/components/breadcrumb/breadcrumb_component.rb +2 -1
- data/templates/components/bulk_action_bar/bulk_action_bar_component.html.erb +1 -1
- data/templates/components/bulk_action_bar/bulk_action_bar_component.rb +4 -2
- data/templates/components/calendar/calendar_component.rb +37 -2
- data/templates/components/carousel/carousel_component.html.erb +5 -5
- data/templates/components/carousel/carousel_component.rb +12 -1
- data/templates/components/cart/cart_component.html.erb +6 -6
- data/templates/components/cart/cart_component.rb +11 -2
- data/templates/components/clipboard/clipboard_component.html.erb +1 -1
- data/templates/components/clipboard/clipboard_component.rb +6 -1
- data/templates/components/collapsible/collapsible_component.rb +1 -1
- data/templates/components/combobox/combobox_component.rb +2 -1
- data/templates/components/command/command_component.rb +3 -1
- data/templates/components/data_table/data_table_component.html.erb +3 -3
- data/templates/components/data_table/data_table_component.rb +16 -1
- data/templates/components/date_picker/date_picker_component.html.erb +2 -2
- data/templates/components/date_picker/date_picker_component.rb +2 -1
- data/templates/components/dialog/dialog_component.html.erb +2 -2
- data/templates/components/filter_bar/filter_bar_component.rb +1 -1
- data/templates/components/form/form_component.html.erb +1 -1
- data/templates/components/form/form_component.rb +21 -0
- data/templates/components/invite_member_dialog/invite_member_dialog_component.html.erb +9 -8
- data/templates/components/invite_member_dialog/invite_member_dialog_component.rb +11 -3
- data/templates/components/native_select/native_select_component.rb +12 -3
- data/templates/components/pagination/pagination_component.html.erb +2 -2
- data/templates/components/pagination/pagination_component.rb +2 -1
- data/templates/components/product_card/product_card_component.html.erb +1 -1
- data/templates/components/product_card/product_card_component.rb +2 -2
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.html.erb +2 -16
- data/templates/components/rich_text_editor_lite/rich_text_editor_lite_component.rb +32 -1
- data/templates/components/search_input/search_input_component.rb +2 -1
- data/templates/components/sheet/sheet_component.html.erb +2 -2
- data/templates/components/sidebar/sidebar_component.html.erb +2 -2
- data/templates/components/sidebar/sidebar_component.rb +4 -1
- data/templates/components/table/table_component.html.erb +1 -1
- data/templates/components/tabs/tabs_component.rb +4 -2
- data/templates/components/theme_toggle/theme_toggle_component.html.erb +17 -3
- data/templates/components/theme_toggle/theme_toggle_component.rb +6 -1
- data/templates/components/top_nav/top_nav_component.html.erb +1 -1
- data/templates/components/top_nav/top_nav_component.rb +3 -2
- data/templates/controllers/accordion_controller.js +68 -3
- data/templates/controllers/api_key_field_controller.js +3 -2
- data/templates/controllers/carousel_controller.js +6 -1
- data/templates/controllers/clipboard_controller.js +3 -3
- data/templates/controllers/rich_text_editor_lite_controller.js +2 -2
- data/templates/controllers/theme_toggle_controller.js +13 -3
- data/templates/locales/senren.en.yml +151 -0
- data/templates/locales/senren.vi.yml +151 -0
- metadata +5 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'senren/rails/safe_write'
|
|
4
|
+
|
|
5
|
+
module Senren
|
|
6
|
+
module Rails
|
|
7
|
+
# Copies the translations the gem ships into the host app's config/locales.
|
|
8
|
+
#
|
|
9
|
+
# The files are not loaded from the gem. A Rails engine auto-loads
|
|
10
|
+
# `config/locales` from its own root, which would put `:vi` into
|
|
11
|
+
# `I18n.available_locales` of every app that merely bundles senren-ui. They
|
|
12
|
+
# are source, like the components: copied once, then the host's to edit.
|
|
13
|
+
#
|
|
14
|
+
# Nothing is needed for English to work. Every component carries its English
|
|
15
|
+
# as the `default:` of the `senren_t` call, so an app with no locale file at
|
|
16
|
+
# all renders what it always did. That is also why English is not copied
|
|
17
|
+
# unless asked for: Rails loads config/locales in name order, so a copied
|
|
18
|
+
# senren.en.yml loads after the host's en.yml and overrides any senren key
|
|
19
|
+
# set there, and it pins the wording, so a default the gem later improves
|
|
20
|
+
# never reaches the app. Ask for `en` to get a copy to translate from.
|
|
21
|
+
#
|
|
22
|
+
# An existing file is never overwritten without `force:`: it is the host's
|
|
23
|
+
# translation, and a re-run of the installer must not undo it.
|
|
24
|
+
class LocaleInstaller
|
|
25
|
+
LOCALE_PATTERN = /\A[a-z]{2,3}(?:-[A-Za-z0-9]+)*\z/
|
|
26
|
+
|
|
27
|
+
attr_reader :paths, :stdout
|
|
28
|
+
|
|
29
|
+
def self.source_dir
|
|
30
|
+
File.join(Senren::Rails.templates_root, 'locales')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The locales the gem ships a file for.
|
|
34
|
+
def self.available
|
|
35
|
+
Dir[File.join(source_dir, 'senren.*.yml')].map { |path| File.basename(path).split('.')[1] }.sort
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(paths: HostPaths.new, stdout: $stdout)
|
|
39
|
+
@paths = paths
|
|
40
|
+
@stdout = stdout
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the locales actually written. Every name is checked before any
|
|
44
|
+
# file is, so a typo in the second does not leave the first half-installed.
|
|
45
|
+
def install(locales: [], force: false)
|
|
46
|
+
names = Array(locales).flatten.map(&:to_s).reject(&:empty?).uniq
|
|
47
|
+
if names.empty?
|
|
48
|
+
stdout.puts " skip no locale named (shipped: #{self.class.available.join(', ')}); English needs no file"
|
|
49
|
+
return []
|
|
50
|
+
end
|
|
51
|
+
names.each { |name| validate!(name) }
|
|
52
|
+
|
|
53
|
+
names.select { |name| copy_locale(name, force: force) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def validate!(name)
|
|
59
|
+
unless LOCALE_PATTERN.match?(name)
|
|
60
|
+
raise ArgumentError, "#{name.inspect} is not a locale name. Expected something like en or pt-BR."
|
|
61
|
+
end
|
|
62
|
+
return if self.class.available.include?(name)
|
|
63
|
+
|
|
64
|
+
raise ArgumentError,
|
|
65
|
+
"Senren ships no #{name} translation (shipped: #{self.class.available.join(', ')}). " \
|
|
66
|
+
"To add #{name}, run bin/rails senren:locales en, copy config/locales/senren.en.yml to " \
|
|
67
|
+
"config/locales/senren.#{name}.yml, change the top-level key to #{name}:, and translate the values."
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def copy_locale(name, force:)
|
|
71
|
+
source = File.join(self.class.source_dir, "senren.#{name}.yml")
|
|
72
|
+
dest = paths.locale_file(name)
|
|
73
|
+
label = "locale #{name}"
|
|
74
|
+
|
|
75
|
+
target = SafeWrite.resolve(dest, paths.root, label, io: stdout)
|
|
76
|
+
return false unless target
|
|
77
|
+
|
|
78
|
+
if File.exist?(target) && !force
|
|
79
|
+
stdout.puts " skip #{dest} (already exists; bin/rails senren:locales #{name} --force replaces it)"
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
SafeWrite.mkdir_p!(paths.locales_dir, paths.root, label)
|
|
84
|
+
SafeWrite.copy!(source, dest, paths.root, label)
|
|
85
|
+
stdout.puts " copy #{dest}"
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/senren/rails/version.rb
CHANGED
data/lib/senren/rails.rb
CHANGED
|
@@ -14,6 +14,7 @@ module Senren
|
|
|
14
14
|
autoload :LlmsWriter, 'senren/rails/llms_writer'
|
|
15
15
|
autoload :Doctor, 'senren/rails/doctor'
|
|
16
16
|
autoload :HostPaths, 'senren/rails/host_paths'
|
|
17
|
+
autoload :LocaleInstaller, 'senren/rails/locale_installer'
|
|
17
18
|
autoload :MarkerBlock, 'senren/rails/marker_block'
|
|
18
19
|
|
|
19
20
|
def self.gem_root
|
data/lib/tasks/senren.rake
CHANGED
|
@@ -18,6 +18,15 @@ namespace :senren do
|
|
|
18
18
|
abort e.message
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
desc 'Copy a shipped translation into config/locales. bin/rails senren:locales vi [--force]'
|
|
22
|
+
task :locales, [:names] do |_t, args|
|
|
23
|
+
options = SenrenRakeArgs.options
|
|
24
|
+
|
|
25
|
+
Senren::Rails::LocaleInstaller.new.install(locales: SenrenRakeArgs.names(args), force: options[:force])
|
|
26
|
+
rescue ArgumentError => e
|
|
27
|
+
abort e.message
|
|
28
|
+
end
|
|
29
|
+
|
|
21
30
|
namespace :skill do
|
|
22
31
|
desc 'Rebuild .senren/skill.md and refresh agent instruction adapters.'
|
|
23
32
|
task sync: :environment do
|
|
@@ -3,13 +3,31 @@
|
|
|
3
3
|
<% expanded = open_item?(item, index) %>
|
|
4
4
|
<section>
|
|
5
5
|
<h3>
|
|
6
|
-
<button type="button" class="flex w-full cursor-pointer items-center justify-between gap-4 px-4 py-3 text-left text-sm font-medium text-[hsl(var(--senren-foreground))] transition-colors hover:bg-[hsl(var(--senren-muted)/0.5)]" aria-expanded="<%= expanded %>" aria-controls="<%= item[:id] %>-panel" data-senren--accordion-target="trigger" data-panel-id="<%= item[:id] %>" data-action="click->senren--accordion#toggle">
|
|
6
|
+
<button type="button" class="group flex w-full cursor-pointer items-center justify-between gap-4 px-4 py-3 text-left text-sm font-medium text-[hsl(var(--senren-foreground))] transition-colors hover:bg-[hsl(var(--senren-muted)/0.5)]" aria-expanded="<%= expanded %>" aria-controls="<%= item[:id] %>-panel" data-senren--accordion-target="trigger" data-panel-id="<%= item[:id] %>" data-action="click->senren--accordion#toggle">
|
|
7
7
|
<span><%= item[:title] %></span>
|
|
8
|
-
|
|
8
|
+
<%# Rotation is driven by aria-expanded, which the controller already %>
|
|
9
|
+
<%# maintains, so the icon cannot drift out of step with the state a %>
|
|
10
|
+
<%# screen reader is being told. A "+" was here before -- readable, but %>
|
|
11
|
+
<%# static, and it gave no hint that the panel animates. %>
|
|
12
|
+
<svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 shrink-0 text-[hsl(var(--senren-muted-foreground))] transition-transform duration-200 group-aria-expanded:rotate-180">
|
|
13
|
+
<path d="m5 8 5 5 5-5"></path>
|
|
14
|
+
</svg>
|
|
9
15
|
</button>
|
|
10
16
|
</h3>
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
<%# The grid wrapper is what animates. A height transition needs a number %>
|
|
18
|
+
<%# to count to, and the content's height is not known until it is laid %>
|
|
19
|
+
<%# out; `grid-template-rows: 0fr -> 1fr` animates to the content's own %>
|
|
20
|
+
<%# height without measuring it in JavaScript. %>
|
|
21
|
+
<%# The `hidden` attribute stays, because content that is visually %>
|
|
22
|
+
<%# collapsed but present is still focusable and still read aloud. The %>
|
|
23
|
+
<%# controller removes it before opening and restores it after the close %>
|
|
24
|
+
<%# transition ends, so the animation never costs the semantics. %>
|
|
25
|
+
<div id="<%= item[:id] %>-panel" data-senren--accordion-target="panel" data-panel-id="<%= item[:id] %>" <% unless expanded %>hidden<% end %> class="grid transition-[grid-template-rows] duration-200 ease-out <%= expanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]' %>">
|
|
26
|
+
<div class="overflow-hidden">
|
|
27
|
+
<div class="px-4 pb-4 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]">
|
|
28
|
+
<%= item[:content].presence || content %>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
13
31
|
</div>
|
|
14
32
|
</section>
|
|
15
33
|
<% end %>
|
|
@@ -22,7 +22,8 @@ module Senren
|
|
|
22
22
|
def normalize_items(items)
|
|
23
23
|
Array(items).map.with_index do |item, index|
|
|
24
24
|
source = item.is_a?(Hash) ? item : { title: item.to_s }
|
|
25
|
-
title = source[:title] || source['title'] ||
|
|
25
|
+
title = source[:title] || source['title'] ||
|
|
26
|
+
senren_t('accordion.section', default: 'Section %{number}', number: index + 1)
|
|
26
27
|
id = (source[:id] || source['id'] || title.to_s.parameterize).to_s
|
|
27
28
|
{ id: id, title: title, content: source[:content] || source['content'] }
|
|
28
29
|
end
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<% else %>
|
|
10
10
|
<button type="button" data-action="click->senren--alert-dialog#open"
|
|
11
11
|
data-senren--alert-dialog-target="trigger"
|
|
12
|
-
class="hidden"
|
|
12
|
+
class="hidden"><%= senren_t('shared.open', default: 'Open') %></button>
|
|
13
13
|
<% end %>
|
|
14
14
|
|
|
15
15
|
<div data-senren--alert-dialog-target="overlay" hidden
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= tag.div(**root_attrs("space-y-2", data: { controller: "senren--api-key-field", "senren--api-key-field-reveal-label-value": reveal_label, "senren--api-key-field-hide-label-value": hide_label, "senren--api-key-field-copy-label-value": copy_label })) do %>
|
|
1
|
+
<%= tag.div(**root_attrs("space-y-2", data: { controller: "senren--api-key-field", "senren--api-key-field-reveal-label-value": reveal_label, "senren--api-key-field-hide-label-value": hide_label, "senren--api-key-field-copy-label-value": copy_label, "senren--api-key-field-copied-status-value": copied_status })) do %>
|
|
2
2
|
<% if content? && value.blank? %>
|
|
3
3
|
<%= content %>
|
|
4
4
|
<% else %>
|
|
@@ -5,7 +5,10 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(value: nil, label: '
|
|
8
|
+
def initialize(value: nil, label: senren_t('api_key_field.label', default: 'API key'),
|
|
9
|
+
reveal_label: senren_t('api_key_field.reveal', default: 'Reveal'),
|
|
10
|
+
hide_label: senren_t('api_key_field.hide', default: 'Hide'),
|
|
11
|
+
copy_label: senren_t('api_key_field.copy', default: 'Copy'),
|
|
9
12
|
class_name: nil, **html)
|
|
10
13
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
11
14
|
@value = value
|
|
@@ -16,5 +19,9 @@ module Senren
|
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
attr_reader :value, :label, :reveal_label, :hide_label, :copy_label
|
|
22
|
+
|
|
23
|
+
# Announced after a copy. The controller reads it from a Stimulus value, so
|
|
24
|
+
# the sentence is built here, in the host's language, not in JavaScript.
|
|
25
|
+
def copied_status = senren_t('api_key_field.copied_status', default: '%{label} complete', label: copy_label)
|
|
19
26
|
end
|
|
20
27
|
end
|
|
@@ -13,7 +13,8 @@ module Senren
|
|
|
13
13
|
}.freeze
|
|
14
14
|
SIZES = { md: '' }.freeze
|
|
15
15
|
|
|
16
|
-
def initialize(content_id: 'senren-main', skip_label: '
|
|
16
|
+
def initialize(content_id: 'senren-main', skip_label: senren_t('app_shell.skip_to_content', default: 'Skip to content'),
|
|
17
|
+
variant: :default, class_name: nil, **html)
|
|
17
18
|
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
18
19
|
@content_id = content_id
|
|
19
20
|
@skip_label = skip_label
|
|
@@ -5,7 +5,8 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(items: [], label: '
|
|
8
|
+
def initialize(items: [], label: senren_t('breadcrumb.label', default: 'Breadcrumb'), separator: '/',
|
|
9
|
+
class_name: nil, **html)
|
|
9
10
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
11
|
@items = normalize_items(items)
|
|
11
12
|
@label = label
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<%= content %>
|
|
4
4
|
<% else %>
|
|
5
5
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
6
|
-
<div class="text-sm font-medium"><%= selection_text ||
|
|
6
|
+
<div class="text-sm font-medium"><%= selection_text || senren_t('bulk_action_bar.ready', default: 'Selection ready') %></div>
|
|
7
7
|
<% if actions? %>
|
|
8
8
|
<div class="flex flex-wrap items-center gap-2"><%= actions %></div>
|
|
9
9
|
<% end %>
|
|
@@ -7,7 +7,8 @@ module Senren
|
|
|
7
7
|
VARIANTS = { default: '' }.freeze
|
|
8
8
|
SIZES = { md: '' }.freeze
|
|
9
9
|
|
|
10
|
-
def initialize(selected_count: nil, item_label: 'items',
|
|
10
|
+
def initialize(selected_count: nil, item_label: senren_t('bulk_action_bar.items', default: 'items'),
|
|
11
|
+
class_name: nil, **html)
|
|
11
12
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
12
13
|
@selected_count = selected_count
|
|
13
14
|
@item_label = item_label
|
|
@@ -18,7 +19,8 @@ module Senren
|
|
|
18
19
|
def selection_text
|
|
19
20
|
return nil if selected_count.nil?
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
senren_t('bulk_action_bar.selected', default: '%{count} %{item_label} selected',
|
|
23
|
+
count: selected_count, item_label: item_label)
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -14,8 +14,43 @@ module Senren
|
|
|
14
14
|
|
|
15
15
|
def month_start = date.beginning_of_month
|
|
16
16
|
def month_end = date.end_of_month
|
|
17
|
-
|
|
18
|
-
def
|
|
17
|
+
|
|
18
|
+
def title
|
|
19
|
+
senren_t('calendar.title', default: '%{month} %{year}',
|
|
20
|
+
month: month_names.fetch(date.month - 1), year: date.strftime('%Y'))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Spelled out call by call rather than looped over a table, so each name is
|
|
24
|
+
# a literal key that bin/i18n-sync can read and a translator can find.
|
|
25
|
+
def month_names
|
|
26
|
+
[
|
|
27
|
+
senren_t('calendar.months.january', default: 'January'),
|
|
28
|
+
senren_t('calendar.months.february', default: 'February'),
|
|
29
|
+
senren_t('calendar.months.march', default: 'March'),
|
|
30
|
+
senren_t('calendar.months.april', default: 'April'),
|
|
31
|
+
senren_t('calendar.months.may', default: 'May'),
|
|
32
|
+
senren_t('calendar.months.june', default: 'June'),
|
|
33
|
+
senren_t('calendar.months.july', default: 'July'),
|
|
34
|
+
senren_t('calendar.months.august', default: 'August'),
|
|
35
|
+
senren_t('calendar.months.september', default: 'September'),
|
|
36
|
+
senren_t('calendar.months.october', default: 'October'),
|
|
37
|
+
senren_t('calendar.months.november', default: 'November'),
|
|
38
|
+
senren_t('calendar.months.december', default: 'December')
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Sunday first, matching calendar_days.
|
|
43
|
+
def weekday_labels
|
|
44
|
+
[
|
|
45
|
+
senren_t('calendar.weekdays.sun', default: 'Sun'),
|
|
46
|
+
senren_t('calendar.weekdays.mon', default: 'Mon'),
|
|
47
|
+
senren_t('calendar.weekdays.tue', default: 'Tue'),
|
|
48
|
+
senren_t('calendar.weekdays.wed', default: 'Wed'),
|
|
49
|
+
senren_t('calendar.weekdays.thu', default: 'Thu'),
|
|
50
|
+
senren_t('calendar.weekdays.fri', default: 'Fri'),
|
|
51
|
+
senren_t('calendar.weekdays.sat', default: 'Sat')
|
|
52
|
+
]
|
|
53
|
+
end
|
|
19
54
|
|
|
20
55
|
def calendar_days
|
|
21
56
|
start_day = month_start.beginning_of_week(:sunday)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= tag.section(**root_attrs("relative overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] text-[hsl(var(--senren-card-foreground))] shadow-sm", data: { controller: "senren--carousel" }, role: "region", "aria-roledescription": "carousel", "aria-label": label)) do %>
|
|
1
|
+
<%= tag.section(**root_attrs("relative overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] text-[hsl(var(--senren-card-foreground))] shadow-sm", data: { controller: "senren--carousel", "senren--carousel-status-template-value": status_template }, role: "region", "aria-roledescription": "carousel", "aria-label": label)) do %>
|
|
2
2
|
<% if content? && slides.empty? %>
|
|
3
3
|
<%= content %>
|
|
4
4
|
<% else %>
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
|
|
46
46
|
<% if slides.size > 1 %>
|
|
47
47
|
<div class="pointer-events-none absolute inset-x-0 top-1/2 z-10 flex -translate-y-1/2 justify-between px-3">
|
|
48
|
-
<button type="button" class="pointer-events-auto inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background)/0.88)] text-[hsl(var(--senren-foreground))] shadow-sm transition hover:-translate-y-0.5 hover:bg-[hsl(var(--senren-accent))] hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]" data-action="click->senren--carousel#previous" aria-label="
|
|
48
|
+
<button type="button" class="pointer-events-auto inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background)/0.88)] text-[hsl(var(--senren-foreground))] shadow-sm transition hover:-translate-y-0.5 hover:bg-[hsl(var(--senren-accent))] hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]" data-action="click->senren--carousel#previous" aria-label="<%= previous_label %>">
|
|
49
49
|
<svg aria-hidden="true" viewBox="0 0 20 20" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
50
50
|
<path d="M12.5 4.5 7 10l5.5 5.5" />
|
|
51
51
|
</svg>
|
|
52
52
|
</button>
|
|
53
|
-
<button type="button" class="pointer-events-auto inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background)/0.88)] text-[hsl(var(--senren-foreground))] shadow-sm transition hover:-translate-y-0.5 hover:bg-[hsl(var(--senren-accent))] hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]" data-action="click->senren--carousel#next" aria-label="
|
|
53
|
+
<button type="button" class="pointer-events-auto inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background)/0.88)] text-[hsl(var(--senren-foreground))] shadow-sm transition hover:-translate-y-0.5 hover:bg-[hsl(var(--senren-accent))] hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]" data-action="click->senren--carousel#next" aria-label="<%= next_label %>">
|
|
54
54
|
<svg aria-hidden="true" viewBox="0 0 20 20" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
55
55
|
<path d="M7.5 4.5 13 10l-5.5 5.5" />
|
|
56
56
|
</svg>
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
|
|
60
60
|
<div class="absolute bottom-3 left-0 right-0 z-10 flex justify-center gap-1.5">
|
|
61
61
|
<% slides.each_with_index do |_slide, index| %>
|
|
62
|
-
<button type="button" class="h-2.5 w-2.5 cursor-pointer rounded-full bg-[hsl(var(--senren-background)/0.72)] ring-1 ring-[hsl(var(--senren-border))] transition-all aria-current:w-6 aria-current:bg-[hsl(var(--senren-primary))]" data-senren--carousel-target="dot" data-index="<%= index %>" data-action="click->senren--carousel#goTo" aria-label="
|
|
62
|
+
<button type="button" class="h-2.5 w-2.5 cursor-pointer rounded-full bg-[hsl(var(--senren-background)/0.72)] ring-1 ring-[hsl(var(--senren-border))] transition-all aria-current:w-6 aria-current:bg-[hsl(var(--senren-primary))]" data-senren--carousel-target="dot" data-index="<%= index %>" data-action="click->senren--carousel#goTo" aria-label="<%= go_to_label(index + 1) %>" aria-current="<%= index.zero? ? 'true' : 'false' %>"></button>
|
|
63
63
|
<% end %>
|
|
64
64
|
</div>
|
|
65
65
|
<% end %>
|
|
66
|
-
<p class="sr-only" aria-live="polite" data-senren--carousel-target="status"
|
|
66
|
+
<p class="sr-only" aria-live="polite" data-senren--carousel-target="status"><%= status_text %></p>
|
|
67
67
|
<% end %>
|
|
68
68
|
<% end %>
|
|
@@ -5,7 +5,7 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(slides: [], label: 'Carousel', class_name: nil, **html)
|
|
8
|
+
def initialize(slides: [], label: senren_t('carousel.label', default: 'Carousel'), class_name: nil, **html)
|
|
9
9
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
10
|
@slides = normalize_slides(slides)
|
|
11
11
|
@label = label
|
|
@@ -13,6 +13,17 @@ module Senren
|
|
|
13
13
|
|
|
14
14
|
attr_reader :slides, :label
|
|
15
15
|
|
|
16
|
+
def previous_label = senren_t('carousel.previous', default: 'Previous slide')
|
|
17
|
+
def next_label = senren_t('carousel.next', default: 'Next slide')
|
|
18
|
+
def go_to_label(number) = senren_t('carousel.go_to', default: 'Go to slide %{number}', number: number)
|
|
19
|
+
|
|
20
|
+
# The announcement as the server first renders it, and the same sentence as a
|
|
21
|
+
# template for the controller, which has to say it again on every slide
|
|
22
|
+
# change. No values are passed for the template, so I18n hands back the
|
|
23
|
+
# `%{current}` and `%{total}` placeholders untouched.
|
|
24
|
+
def status_text = senren_t('carousel.status', default: 'Slide %{current} of %{total}', current: 1, total: slides.size)
|
|
25
|
+
def status_template = senren_t('carousel.status', default: 'Slide %{current} of %{total}')
|
|
26
|
+
|
|
16
27
|
private
|
|
17
28
|
|
|
18
29
|
def normalize_slides(slides)
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
<p class="text-sm text-[hsl(var(--senren-muted-foreground))]" data-senren--cart-target="lineTotal"><%= line_total(item) %></p>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
|
-
<div class="flex items-center gap-1" role="group" aria-label="<%= item
|
|
33
|
+
<div class="flex items-center gap-1" role="group" aria-label="<%= quantity_label(item) %>">
|
|
34
34
|
<button type="button"
|
|
35
35
|
data-action="click->senren--cart#decrement"
|
|
36
|
-
aria-label="
|
|
36
|
+
aria-label="<%= decrease_label(item) %>"
|
|
37
37
|
class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-(--senren-radius) border border-[hsl(var(--senren-border))] text-sm hover:bg-[hsl(var(--senren-accent))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]">−</button>
|
|
38
38
|
|
|
39
39
|
<output class="w-8 text-center text-sm tabular-nums text-[hsl(var(--senren-foreground))]"
|
|
@@ -41,21 +41,21 @@
|
|
|
41
41
|
|
|
42
42
|
<button type="button"
|
|
43
43
|
data-action="click->senren--cart#increment"
|
|
44
|
-
aria-label="
|
|
44
|
+
aria-label="<%= increase_label(item) %>"
|
|
45
45
|
class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-(--senren-radius) border border-[hsl(var(--senren-border))] text-sm hover:bg-[hsl(var(--senren-accent))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]">+</button>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
<button type="button"
|
|
49
49
|
data-action="click->senren--cart#remove"
|
|
50
|
-
aria-label="
|
|
51
|
-
class="inline-flex h-8 cursor-pointer items-center rounded-(--senren-radius) px-2 text-sm text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-destructive))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]"
|
|
50
|
+
aria-label="<%= remove_item_label(item) %>"
|
|
51
|
+
class="inline-flex h-8 cursor-pointer items-center rounded-(--senren-radius) px-2 text-sm text-[hsl(var(--senren-muted-foreground))] hover:bg-[hsl(var(--senren-accent))] hover:text-[hsl(var(--senren-destructive))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--senren-ring))]"><%= remove_label %></button>
|
|
52
52
|
</li>
|
|
53
53
|
<% end %>
|
|
54
54
|
</ul>
|
|
55
55
|
|
|
56
56
|
<footer class="flex items-center justify-between gap-3 border-t border-[hsl(var(--senren-border))] px-4 py-3">
|
|
57
57
|
<div class="text-sm text-[hsl(var(--senren-muted-foreground))]">
|
|
58
|
-
|
|
58
|
+
<%= subtotal_label %>
|
|
59
59
|
<span class="ml-1 font-semibold text-[hsl(var(--senren-foreground))]" data-senren--cart-target="subtotal"><%= subtotal %></span>
|
|
60
60
|
</div>
|
|
61
61
|
|
|
@@ -22,8 +22,9 @@ module Senren
|
|
|
22
22
|
|
|
23
23
|
SIZES = { md: '' }.freeze
|
|
24
24
|
|
|
25
|
-
def initialize(items: [], currency: '$', title: '
|
|
26
|
-
|
|
25
|
+
def initialize(items: [], currency: '$', title: senren_t('cart.title', default: 'Cart'),
|
|
26
|
+
empty_text: senren_t('cart.empty', default: 'Your cart is empty.'),
|
|
27
|
+
checkout_url: nil, checkout_label: senren_t('cart.checkout', default: 'Checkout'), variant: :default,
|
|
27
28
|
id: nil, class_name: nil, **html)
|
|
28
29
|
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
29
30
|
@items = Array(items).map { |item| normalize_item(item) }
|
|
@@ -37,6 +38,14 @@ module Senren
|
|
|
37
38
|
|
|
38
39
|
attr_reader :items, :currency, :title, :empty_text, :checkout_label, :dom_id
|
|
39
40
|
|
|
41
|
+
def subtotal_label = senren_t('cart.subtotal', default: 'Subtotal')
|
|
42
|
+
def remove_label = senren_t('cart.remove', default: 'Remove')
|
|
43
|
+
|
|
44
|
+
def quantity_label(item) = senren_t('cart.quantity_group', default: '%{name} quantity', name: item[:name])
|
|
45
|
+
def decrease_label(item) = senren_t('cart.decrease', default: 'Decrease %{name} quantity', name: item[:name])
|
|
46
|
+
def increase_label(item) = senren_t('cart.increase', default: 'Increase %{name} quantity', name: item[:name])
|
|
47
|
+
def remove_item_label(item) = senren_t('cart.remove_item', default: 'Remove %{name}', name: item[:name])
|
|
48
|
+
|
|
40
49
|
def empty? = items.empty?
|
|
41
50
|
def total_quantity = items.sum { |item| item[:quantity] }
|
|
42
51
|
def subtotal_cents = items.sum { |item| item[:price_cents] * item[:quantity] }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= tag.div(**root_attrs("inline-flex items-center gap-2", data: { controller: "senren--clipboard", "senren--clipboard-copied-label-value": copied_label })) do %>
|
|
1
|
+
<%= tag.div(**root_attrs("inline-flex items-center gap-2", data: { controller: "senren--clipboard", "senren--clipboard-copied-label-value": copied_label, "senren--clipboard-copied-status-value": copied_status })) do %>
|
|
2
2
|
<input type="text" readonly autocomplete="off" aria-label="<%= label %>" value="<%= value %>" data-senren--clipboard-target="source" class="h-10 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-muted)/0.4)] px-3 font-mono text-sm text-[hsl(var(--senren-foreground))]">
|
|
3
3
|
<button type="button" data-senren--clipboard-target="button" data-action="click->senren--clipboard#copy" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-3 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90">
|
|
4
4
|
<%= label %>
|
|
@@ -5,7 +5,8 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(value:, label: '
|
|
8
|
+
def initialize(value:, label: senren_t('clipboard.copy', default: 'Copy'),
|
|
9
|
+
copied_label: senren_t('clipboard.copied', default: 'Copied'), class_name: nil, **html)
|
|
9
10
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
11
|
@label = label
|
|
11
12
|
@value = value
|
|
@@ -13,5 +14,9 @@ module Senren
|
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
attr_reader :label, :value, :copied_label
|
|
17
|
+
|
|
18
|
+
# What a screen reader is told once the copy has happened. Passed to the
|
|
19
|
+
# controller as a Stimulus value so no sentence lives in JavaScript.
|
|
20
|
+
def copied_status = senren_t('clipboard.copied_status', default: 'Copied to clipboard')
|
|
16
21
|
end
|
|
17
22
|
end
|
|
@@ -6,7 +6,7 @@ module Senren
|
|
|
6
6
|
VARIANTS = { default: '' }.freeze
|
|
7
7
|
SIZES = { md: '' }.freeze
|
|
8
8
|
|
|
9
|
-
def initialize(title: 'Details', open: false, class_name: nil, **html)
|
|
9
|
+
def initialize(title: senren_t('collapsible.details', default: 'Details'), open: false, class_name: nil, **html)
|
|
10
10
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
11
11
|
@title = title
|
|
12
12
|
@open = open
|
|
@@ -8,7 +8,8 @@ module Senren
|
|
|
8
8
|
}.freeze
|
|
9
9
|
SIZES = { md: '' }.freeze
|
|
10
10
|
|
|
11
|
-
def initialize(name:, options:, value: nil, placeholder: '
|
|
11
|
+
def initialize(name:, options:, value: nil, placeholder: senren_t('combobox.placeholder', default: 'Search...'),
|
|
12
|
+
variant: :default, class_name: nil, **html)
|
|
12
13
|
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
13
14
|
@name = name
|
|
14
15
|
@options = normalize_options(options)
|
|
@@ -5,7 +5,9 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(items: [], placeholder: '
|
|
8
|
+
def initialize(items: [], placeholder: senren_t('command.placeholder', default: 'Type a command...'),
|
|
9
|
+
label: senren_t('command.label', default: 'Command menu'),
|
|
10
|
+
empty_text: senren_t('command.empty', default: 'No results found.'),
|
|
9
11
|
id: nil, class_name: nil, **html)
|
|
10
12
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
11
13
|
@placeholder = placeholder
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= tag.div(**root_attrs("overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] shadow-sm", data: { controller: "senren--data-table" })) do %>
|
|
1
|
+
<%= tag.div(**root_attrs("relative overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] shadow-sm", data: { controller: "senren--data-table" })) do %>
|
|
2
2
|
<% if toolbar? %>
|
|
3
3
|
<div class="border-b border-[hsl(var(--senren-border))] p-3"><%= toolbar %></div>
|
|
4
4
|
<% end %>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<% if sortable? %>
|
|
19
19
|
<button type="button" class="inline-flex cursor-pointer items-center gap-1 rounded-sm hover:text-[hsl(var(--senren-foreground))]" data-action="click->senren--data-table#sort" data-sort-key="<%= column_sort_key(column) %>">
|
|
20
20
|
<%= column_label(column) %>
|
|
21
|
-
<span aria-hidden="true"
|
|
21
|
+
<span aria-hidden="true"><%= senren_t('data_table.sort', default: 'Sort') %></span>
|
|
22
22
|
</button>
|
|
23
23
|
<% else %>
|
|
24
24
|
<%= column_label(column) %>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<tr class="transition-colors hover:bg-[hsl(var(--senren-muted)/0.35)]" data-senren--data-table-target="row">
|
|
33
33
|
<% columns.each do |column| %>
|
|
34
34
|
<% value = cell_value(row, column) %>
|
|
35
|
-
<td class="px-4 py-3" data-sort-key="<%= column_sort_key(column) %>" data-sort-value="<%= value %>"><%= value %></td>
|
|
35
|
+
<td class="px-4 py-3" data-sort-key="<%= column_sort_key(column) %>" data-sort-value="<%= sort_value(value) %>"><%= value %></td>
|
|
36
36
|
<% end %>
|
|
37
37
|
</tr>
|
|
38
38
|
<% end %>
|
|
@@ -6,7 +6,8 @@ module Senren
|
|
|
6
6
|
VARIANTS = { default: '' }.freeze
|
|
7
7
|
SIZES = { md: '' }.freeze
|
|
8
8
|
|
|
9
|
-
def initialize(columns: [], rows: [], caption: nil,
|
|
9
|
+
def initialize(columns: [], rows: [], caption: nil,
|
|
10
|
+
empty_text: senren_t('data_table.empty', default: 'No records found.'), sortable: true,
|
|
10
11
|
variant: :default, class_name: nil, **html)
|
|
11
12
|
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
12
13
|
@columns = Array(columns)
|
|
@@ -33,6 +34,20 @@ module Senren
|
|
|
33
34
|
column_key(column).to_s
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# The text a cell shows, for its data-sort-value. A cell holding markup (a
|
|
38
|
+
# badge, a link) is html_safe, which ERB does not escape, so writing it
|
|
39
|
+
# into the attribute as-is pasted the markup inside the quotes and broke
|
|
40
|
+
# the <td>. Its decoded text is a plain String, which ERB escapes once.
|
|
41
|
+
#
|
|
42
|
+
# Asks the string, not the value: ERB decides on `to_s.html_safe?`, so an
|
|
43
|
+
# object whose to_s returns markup breaks the attribute just the same.
|
|
44
|
+
def sort_value(value)
|
|
45
|
+
text = value.to_s
|
|
46
|
+
return text unless text.html_safe?
|
|
47
|
+
|
|
48
|
+
Nokogiri::HTML5.fragment(text.to_str).text
|
|
49
|
+
end
|
|
50
|
+
|
|
36
51
|
private
|
|
37
52
|
|
|
38
53
|
def column_key(column)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%= tag.div(**root_attrs("flex w-full items-center gap-2", data: { controller: "senren--date-picker" })) do %>
|
|
2
2
|
<input type="date" autocomplete="off" id="<%= id %>" name="<%= name %>" value="<%= value %>" placeholder="<%= placeholder %>" data-senren--date-picker-target="input" class="h-10 w-full max-w-56 cursor-pointer 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))] [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:opacity-60 [&::-webkit-calendar-picker-indicator]:hover:opacity-100 <%= 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))]"
|
|
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))]"
|
|
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))]"><%= senren_t('date_picker.today', default: '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))]"><%= senren_t('date_picker.clear', default: 'Clear') %></button>
|
|
5
5
|
<% end %>
|
|
@@ -8,7 +8,8 @@ module Senren
|
|
|
8
8
|
}.freeze
|
|
9
9
|
SIZES = { md: '' }.freeze
|
|
10
10
|
|
|
11
|
-
def initialize(name:, value: nil, id: nil, placeholder: '
|
|
11
|
+
def initialize(name:, value: nil, id: nil, placeholder: senren_t('date_picker.placeholder', default: 'Select date'),
|
|
12
|
+
variant: :default, class_name: nil, **html)
|
|
12
13
|
super(variant: variant, size: :md, class_name: class_name, **html)
|
|
13
14
|
@name = name
|
|
14
15
|
@value = value
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</span>
|
|
9
9
|
<% else %>
|
|
10
10
|
<button type="button" data-action="click->senren--dialog#open"
|
|
11
|
-
class="hidden" data-senren--dialog-target="trigger" aria-controls="<%= dom_id %>-panel"
|
|
11
|
+
class="hidden" data-senren--dialog-target="trigger" aria-controls="<%= dom_id %>-panel"><%= senren_t('shared.open', default: 'Open') %></button>
|
|
12
12
|
<% end %>
|
|
13
13
|
|
|
14
14
|
<div data-senren--dialog-target="overlay" hidden
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<% end %>
|
|
35
35
|
|
|
36
36
|
<button type="button" data-action="click->senren--dialog#close"
|
|
37
|
-
aria-label="Close"
|
|
37
|
+
aria-label="<%= senren_t('shared.close', default: 'Close') %>"
|
|
38
38
|
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))]">
|
|
39
39
|
<span aria-hidden="true">×</span>
|
|
40
40
|
</button>
|
|
@@ -5,7 +5,7 @@ module Senren
|
|
|
5
5
|
VARIANTS = { default: '' }.freeze
|
|
6
6
|
SIZES = { md: '' }.freeze
|
|
7
7
|
|
|
8
|
-
def initialize(label: 'Filters', class_name: nil, **html)
|
|
8
|
+
def initialize(label: senren_t('filter_bar.label', default: 'Filters'), class_name: nil, **html)
|
|
9
9
|
super(variant: :default, size: :md, class_name: class_name, **html)
|
|
10
10
|
@label = label
|
|
11
11
|
end
|