feat(combobox): implement ComboboxComponent
This commit is contained in:
parent
bea8cba6ce
commit
85024174d4
7 changed files with 227 additions and 1 deletions
|
@ -621,3 +621,18 @@ textarea::placeholder {
|
|||
background-color: $white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fr-menu__list {
|
||||
padding: $default-spacer;
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
|
||||
.fr-menu__item {
|
||||
list-style-type: none;
|
||||
margin-bottom: $default-spacer;
|
||||
|
||||
&[aria-selected] {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
app/components/dsfr/combobox_component.rb
Normal file
65
app/components/dsfr/combobox_component.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
class Dsfr::ComboboxComponent < ApplicationComponent
|
||||
def initialize(form: nil, options:, selected: nil, allows_custom_value: false, **html_options)
|
||||
@form, @options, @selected, @allows_custom_value, @html_options = form, options, selected, allows_custom_value, html_options
|
||||
end
|
||||
|
||||
attr_reader :form, :options, :selected, :allows_custom_value
|
||||
|
||||
private
|
||||
|
||||
def name
|
||||
@html_options[:name]
|
||||
end
|
||||
|
||||
def form_id
|
||||
@html_options[:form_id]
|
||||
end
|
||||
|
||||
def html_input_options
|
||||
{
|
||||
type: 'text',
|
||||
autocomplete: 'off',
|
||||
spellcheck: 'false',
|
||||
id: input_id,
|
||||
class: input_class,
|
||||
value: input_value,
|
||||
'aria-expanded': 'false',
|
||||
'aria-describedby': @html_options[:describedby]
|
||||
}.compact
|
||||
end
|
||||
|
||||
def input_id
|
||||
@html_options[:id]
|
||||
end
|
||||
|
||||
def input_class
|
||||
"#{@html_options[:class].presence || ''} fr-select"
|
||||
end
|
||||
|
||||
def input_value
|
||||
selected.present? ? options_with_values.find { _1.last == selected }&.first : nil
|
||||
end
|
||||
|
||||
def list_id
|
||||
input_id.present? ? "#{input_id}-list" : nil
|
||||
end
|
||||
|
||||
def options_with_values
|
||||
options.map { _1.is_a?(Array) ? _1 : [_1, _1] }
|
||||
end
|
||||
|
||||
def options_json
|
||||
options_with_values.map { |(label, value)| { label:, value: } }.to_json
|
||||
end
|
||||
|
||||
def hints_json
|
||||
{
|
||||
empty: t(".sr.results", count: 0),
|
||||
one: t(".sr.results", count: 1),
|
||||
many: t(".sr.results", count: 2),
|
||||
oneWithLabel: t(".sr.results_with_label", count: 1),
|
||||
manyWithLabel: t(".sr.results_with_label", count: 2),
|
||||
selected: t(".sr.selected", count: 2)
|
||||
}.to_json
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
en:
|
||||
sr:
|
||||
results:
|
||||
zero: No result
|
||||
one: 1 result
|
||||
other: "{count} results"
|
||||
results_with_label:
|
||||
one: "1 result. {label} is the top result – press Enter to activate"
|
||||
other: "{count} results. {label} is the top result – press Enter to activate"
|
||||
selected: "{label} selected"
|
|
@ -0,0 +1,10 @@
|
|||
fr:
|
||||
sr:
|
||||
results:
|
||||
zero: Aucun résultat
|
||||
one: 1 résultat
|
||||
other: "{count} résultats"
|
||||
results_with_label:
|
||||
one: "1 résultat. {label} est le premier résultat – appuyez sur Entrée pour sélectionner"
|
||||
other: "{count} résultats. {label} est le premier résultat – appuyez sur Entrée pour sélectionner"
|
||||
selected: "{label} sélectionné"
|
|
@ -0,0 +1,13 @@
|
|||
.fr-ds-combobox{ data: { controller: 'combobox', allows_custom_value: allows_custom_value } }
|
||||
.fr-ds-combobox-input
|
||||
%input{ **html_input_options }
|
||||
- if form
|
||||
= form.hidden_field name, value: selected, form: form_id
|
||||
- else
|
||||
%input{ type: 'hidden', name:, value: selected, form: form_id }
|
||||
.fr-menu
|
||||
%ul.fr-menu__list.hidden{ role: 'listbox', hidden: true, id: list_id, data: { turbo_force: :browser, options: options_json, selected:, hints: hints_json } }
|
||||
.sr-only{ aria: { live: 'polite', atomic: 'true' }, data: { turbo_force: :browser } }
|
||||
%template
|
||||
%li.fr-menu__item{ role: 'option' }
|
||||
%slot{ name: 'label' }
|
|
@ -14,6 +14,7 @@
|
|||
= render partial: "layouts/favicons"
|
||||
|
||||
= vite_client_tag
|
||||
= vite_react_refresh_tag
|
||||
= vite_javascript_tag 'application'
|
||||
|
||||
= preload_link_tag(asset_url("Marianne-Regular.woff2"))
|
||||
|
@ -22,7 +23,7 @@
|
|||
= vite_stylesheet_tag 'main', media: 'all'
|
||||
= stylesheet_link_tag 'application', media: 'all'
|
||||
|
||||
%body{ class: browser.platform.ios? ? 'ios' : nil }
|
||||
%body{ class: browser.platform.ios? ? 'ios' : nil, data: { controller: 'turbo' } }
|
||||
.page-wrapper
|
||||
%main.m-6
|
||||
= content_for?(:content) ? yield(:content) : yield
|
||||
|
|
112
spec/components/previews/dsfr/combobox_component_preview.rb
Normal file
112
spec/components/previews/dsfr/combobox_component_preview.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
class Dsfr::ComboboxComponentPreview < ViewComponent::Preview
|
||||
OPTIONS = [
|
||||
'Cheddar',
|
||||
'Brie',
|
||||
'Mozzarella',
|
||||
'Gouda',
|
||||
'Swiss',
|
||||
'Parmesan',
|
||||
'Feta',
|
||||
'Blue cheese',
|
||||
'Camembert',
|
||||
'Monterey Jack',
|
||||
'Roquefort',
|
||||
'Provolone',
|
||||
'Colby',
|
||||
'Havarti',
|
||||
'Ricotta',
|
||||
'Pepper Jack',
|
||||
'Muenster',
|
||||
'Fontina',
|
||||
'Limburger',
|
||||
'Asiago',
|
||||
'Cottage cheese',
|
||||
'Emmental',
|
||||
'Mascarpone',
|
||||
'Taleggio',
|
||||
'Gruyere',
|
||||
'Edam',
|
||||
'Pecorino Romano',
|
||||
'Manchego',
|
||||
'Halloumi',
|
||||
'Jarlsberg',
|
||||
'Munster',
|
||||
'Stilton',
|
||||
'Gorgonzola',
|
||||
'Queso blanco',
|
||||
'Queso fresco',
|
||||
'Queso de bola',
|
||||
'Queso de cabra',
|
||||
'Queso panela',
|
||||
'Queso Oaxaca',
|
||||
'Queso Chihuahua',
|
||||
'Queso manchego',
|
||||
'Queso de bola',
|
||||
'Queso de bola de cabra',
|
||||
'Queso de bola de vaca',
|
||||
'Queso de bola de oveja',
|
||||
'Queso de bola de mezcla',
|
||||
'Queso de bola de leche cruda',
|
||||
'Queso de bola de leche pasteurizada',
|
||||
'Queso de bola de leche de cabra',
|
||||
'Queso de bola de leche de vaca',
|
||||
'Queso de bola de leche de oveja',
|
||||
'Queso de bola de leche de mezcla',
|
||||
'Burrata',
|
||||
'Scamorza',
|
||||
'Caciocavallo',
|
||||
'Provolone piccante',
|
||||
'Pecorino sardo',
|
||||
'Pecorino toscano',
|
||||
'Pecorino siciliano',
|
||||
'Pecorino calabrese',
|
||||
'Pecorino moliterno',
|
||||
'Pecorino di fossa',
|
||||
'Pecorino di filiano',
|
||||
'Pecorino di pienza',
|
||||
'Pecorino di grotta',
|
||||
'Pecorino di capra',
|
||||
'Pecorino di mucca',
|
||||
'Pecorino di pecora',
|
||||
'Pecorino di bufala',
|
||||
'Cacio di bosco',
|
||||
'Cacio di roma',
|
||||
'Cacio di fossa',
|
||||
'Cacio di tricarico',
|
||||
'Cacio di cavallo',
|
||||
'Cacio di capra',
|
||||
'Cacio di mucca',
|
||||
'Cacio di pecora',
|
||||
'Cacio di bufala',
|
||||
'Taleggio di capra',
|
||||
'Taleggio di mucca',
|
||||
'Taleggio di pecora',
|
||||
'Taleggio di bufala',
|
||||
'Bel Paese',
|
||||
'Crescenza',
|
||||
'Stracchino',
|
||||
'Robiola',
|
||||
'Toma',
|
||||
'Bra',
|
||||
'Castelmagno',
|
||||
'Raschera',
|
||||
'Montasio',
|
||||
'Piave',
|
||||
'Bitto',
|
||||
'Quartirolo Lombardo',
|
||||
'Formaggella del Luinese',
|
||||
'Formaggella della Val Vigezzo',
|
||||
'Formaggella della Valle Grana',
|
||||
'Formaggella della Val Bognanco',
|
||||
'Formaggella della Val d’Intelvi',
|
||||
'Formaggella della Val Gerola'
|
||||
]
|
||||
|
||||
def simple_select_with_options
|
||||
render Dsfr::ComboboxComponent.new(name: :value, options: OPTIONS, selected: OPTIONS.sample, id: 'simple-select', class: 'width-33')
|
||||
end
|
||||
|
||||
def simple_select_with_options_and_allows_custom_value
|
||||
render Dsfr::ComboboxComponent.new(name: :value, options: OPTIONS, selected: OPTIONS.sample, id: 'simple-select', class: 'width-33', allows_custom_value: true)
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue