shadcn_view_components 0.2.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 +590 -0
- data/LICENSE +49 -0
- data/README.md +542 -0
- data/app/assets/javascripts/shadcn/aria_relationships.js +88 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +241 -0
- data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +98 -0
- data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +305 -0
- data/app/assets/javascripts/shadcn/controllers/checked_state_controller.js +121 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +613 -0
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +87 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +208 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +145 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +416 -0
- data/app/assets/javascripts/shadcn/controllers/menu_controller.js +286 -0
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +430 -0
- data/app/assets/javascripts/shadcn/controllers/message_scroller_controller.js +37 -0
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +254 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +117 -0
- data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +256 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +491 -0
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +30 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.js +74 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +146 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.js +81 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +21 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +37 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +123 -0
- data/app/assets/javascripts/shadcn/floating_position.js +660 -0
- data/app/assets/javascripts/shadcn/hide_after_exit.js +53 -0
- data/app/assets/javascripts/shadcn/index.js +65 -0
- data/app/assets/javascripts/shadcn/menu_popover.js +352 -0
- data/app/assets/javascripts/shadcn/package.json +17 -0
- data/app/assets/javascripts/shadcn/state_attrs.js +13 -0
- data/app/assets/stylesheets/shadcn/shadcn.css +766 -0
- data/app/assets/tailwind/shadcn_view_components/engine.css +11 -0
- data/app/components/shadcn/accordion.rb +121 -0
- data/app/components/shadcn/alert.rb +31 -0
- data/app/components/shadcn/alert_dialog.rb +165 -0
- data/app/components/shadcn/aspect_ratio.rb +34 -0
- data/app/components/shadcn/attachment.rb +86 -0
- data/app/components/shadcn/avatar.rb +55 -0
- data/app/components/shadcn/badge.rb +32 -0
- data/app/components/shadcn/base_component.rb +239 -0
- data/app/components/shadcn/breadcrumb.rb +70 -0
- data/app/components/shadcn/bubble.rb +48 -0
- data/app/components/shadcn/button.html.erb +1 -0
- data/app/components/shadcn/button.rb +41 -0
- data/app/components/shadcn/button_group.rb +41 -0
- data/app/components/shadcn/button_styled.rb +51 -0
- data/app/components/shadcn/calendar.rb +276 -0
- data/app/components/shadcn/card.rb +18 -0
- data/app/components/shadcn/carousel.rb +236 -0
- data/app/components/shadcn/chart.rb +57 -0
- data/app/components/shadcn/checkbox.rb +64 -0
- data/app/components/shadcn/collapsible.rb +25 -0
- data/app/components/shadcn/combobox.rb +552 -0
- data/app/components/shadcn/command.rb +149 -0
- data/app/components/shadcn/context_menu.rb +79 -0
- data/app/components/shadcn/dialog.rb +180 -0
- data/app/components/shadcn/direction_provider.rb +18 -0
- data/app/components/shadcn/drawer.rb +150 -0
- data/app/components/shadcn/dropdown_menu.rb +242 -0
- data/app/components/shadcn/empty.rb +40 -0
- data/app/components/shadcn/field.rb +106 -0
- data/app/components/shadcn/floating_position_options.rb +132 -0
- data/app/components/shadcn/form.rb +105 -0
- data/app/components/shadcn/hover_card.rb +66 -0
- data/app/components/shadcn/input.rb +13 -0
- data/app/components/shadcn/input_group/button.rb +48 -0
- data/app/components/shadcn/input_group.rb +51 -0
- data/app/components/shadcn/input_otp.rb +265 -0
- data/app/components/shadcn/item.rb +108 -0
- data/app/components/shadcn/kbd.rb +9 -0
- data/app/components/shadcn/label.rb +12 -0
- data/app/components/shadcn/marker.rb +37 -0
- data/app/components/shadcn/menubar.rb +110 -0
- data/app/components/shadcn/message.rb +29 -0
- data/app/components/shadcn/message_scroller.rb +52 -0
- data/app/components/shadcn/native_checked_state.rb +74 -0
- data/app/components/shadcn/native_select.rb +78 -0
- data/app/components/shadcn/navigation_menu.rb +122 -0
- data/app/components/shadcn/pagination.rb +160 -0
- data/app/components/shadcn/popover.rb +68 -0
- data/app/components/shadcn/preview_base.rb +107 -0
- data/app/components/shadcn/progress.rb +49 -0
- data/app/components/shadcn/radio_group.rb +74 -0
- data/app/components/shadcn/resizable.rb +77 -0
- data/app/components/shadcn/scroll_area.rb +59 -0
- data/app/components/shadcn/select.rb +356 -0
- data/app/components/shadcn/separator.rb +42 -0
- data/app/components/shadcn/sheet.rb +157 -0
- data/app/components/shadcn/sidebar.rb +332 -0
- data/app/components/shadcn/skeleton.rb +13 -0
- data/app/components/shadcn/slider.rb +279 -0
- data/app/components/shadcn/sonner.rb +30 -0
- data/app/components/shadcn/spinner.rb +32 -0
- data/app/components/shadcn/switch.rb +79 -0
- data/app/components/shadcn/table.rb +46 -0
- data/app/components/shadcn/tabs.rb +119 -0
- data/app/components/shadcn/textarea.rb +7 -0
- data/app/components/shadcn/toggle.rb +66 -0
- data/app/components/shadcn/toggle_group.rb +127 -0
- data/app/components/shadcn/tooltip.rb +74 -0
- data/config/importmap.rb +5 -0
- data/lib/generators/shadcn_view_components/install_generator.rb +182 -0
- data/lib/shadcn_view_components/classes.rb +89 -0
- data/lib/shadcn_view_components/component_naming.rb +21 -0
- data/lib/shadcn_view_components/contracts/calendar.rb +95 -0
- data/lib/shadcn_view_components/engine.rb +56 -0
- data/lib/shadcn_view_components/generated/contracts/accordion.rb +112 -0
- data/lib/shadcn_view_components/generated/contracts/alert-dialog.rb +300 -0
- data/lib/shadcn_view_components/generated/contracts/alert.rb +104 -0
- data/lib/shadcn_view_components/generated/contracts/aspect-ratio.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/attachment.rb +224 -0
- data/lib/shadcn_view_components/generated/contracts/avatar.rb +149 -0
- data/lib/shadcn_view_components/generated/contracts/badge.rb +39 -0
- data/lib/shadcn_view_components/generated/contracts/breadcrumb.rb +172 -0
- data/lib/shadcn_view_components/generated/contracts/bubble.rb +112 -0
- data/lib/shadcn_view_components/generated/contracts/button-group.rb +81 -0
- data/lib/shadcn_view_components/generated/contracts/button.rb +81 -0
- data/lib/shadcn_view_components/generated/contracts/card.rb +172 -0
- data/lib/shadcn_view_components/generated/contracts/carousel.rb +133 -0
- data/lib/shadcn_view_components/generated/contracts/chart.rb +109 -0
- data/lib/shadcn_view_components/generated/contracts/checkbox.rb +35 -0
- data/lib/shadcn_view_components/generated/contracts/collapsible.rb +89 -0
- data/lib/shadcn_view_components/generated/contracts/combobox.rb +368 -0
- data/lib/shadcn_view_components/generated/contracts/command.rb +222 -0
- data/lib/shadcn_view_components/generated/contracts/context-menu.rb +378 -0
- data/lib/shadcn_view_components/generated/contracts/dialog.rb +258 -0
- data/lib/shadcn_view_components/generated/contracts/direction.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/drawer.rb +280 -0
- data/lib/shadcn_view_components/generated/contracts/dropdown-menu.rb +380 -0
- data/lib/shadcn_view_components/generated/contracts/empty.rb +150 -0
- data/lib/shadcn_view_components/generated/contracts/field.rb +244 -0
- data/lib/shadcn_view_components/generated/contracts/form.rb +62 -0
- data/lib/shadcn_view_components/generated/contracts/hover-card.rb +90 -0
- data/lib/shadcn_view_components/generated/contracts/input-group.rb +155 -0
- data/lib/shadcn_view_components/generated/contracts/input-otp.rb +106 -0
- data/lib/shadcn_view_components/generated/contracts/input.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/item.rb +251 -0
- data/lib/shadcn_view_components/generated/contracts/kbd.rb +57 -0
- data/lib/shadcn_view_components/generated/contracts/label.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/marker.rb +82 -0
- data/lib/shadcn_view_components/generated/contracts/menubar.rb +394 -0
- data/lib/shadcn_view_components/generated/contracts/message-scroller.rb +149 -0
- data/lib/shadcn_view_components/generated/contracts/message.rb +149 -0
- data/lib/shadcn_view_components/generated/contracts/native-select.rb +82 -0
- data/lib/shadcn_view_components/generated/contracts/navigation-menu.rb +195 -0
- data/lib/shadcn_view_components/generated/contracts/pagination.rb +176 -0
- data/lib/shadcn_view_components/generated/contracts/popover.rb +159 -0
- data/lib/shadcn_view_components/generated/contracts/progress.rb +126 -0
- data/lib/shadcn_view_components/generated/contracts/radio-group.rb +58 -0
- data/lib/shadcn_view_components/generated/contracts/resizable.rb +85 -0
- data/lib/shadcn_view_components/generated/contracts/scroll-area.rb +59 -0
- data/lib/shadcn_view_components/generated/contracts/select.rb +245 -0
- data/lib/shadcn_view_components/generated/contracts/separator.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/sheet.rb +209 -0
- data/lib/shadcn_view_components/generated/contracts/sidebar.rb +558 -0
- data/lib/shadcn_view_components/generated/contracts/skeleton.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/slider.rb +38 -0
- data/lib/shadcn_view_components/generated/contracts/sonner.rb +36 -0
- data/lib/shadcn_view_components/generated/contracts/spinner.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/switch.rb +35 -0
- data/lib/shadcn_view_components/generated/contracts/table.rb +199 -0
- data/lib/shadcn_view_components/generated/contracts/tabs.rb +104 -0
- data/lib/shadcn_view_components/generated/contracts/textarea.rb +34 -0
- data/lib/shadcn_view_components/generated/contracts/toggle-group.rb +62 -0
- data/lib/shadcn_view_components/generated/contracts/toggle.rb +39 -0
- data/lib/shadcn_view_components/generated/contracts/tooltip.rb +116 -0
- data/lib/shadcn_view_components/numeric_property_validation.rb +124 -0
- data/lib/shadcn_view_components/property_contract_definitions.rb +92 -0
- data/lib/shadcn_view_components/property_contracts.rb +42 -0
- data/lib/shadcn_view_components/property_validation.rb +55 -0
- data/lib/shadcn_view_components/version.rb +6 -0
- data/lib/shadcn_view_components.rb +21 -0
- metadata +446 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# 実inputを値・selection・フォーム送信の唯一の情報源にし、Slot群はその派生表示とする。
|
|
6
|
+
# inputMode=numericはソフトウェアキーボードへのhintであり、入力制限は利用者指定patternが担う。
|
|
7
|
+
class InputOTP < BaseComponent
|
|
8
|
+
CONTROLLER = "shadcn--input-otp"
|
|
9
|
+
|
|
10
|
+
module Markup
|
|
11
|
+
CONTAINER_CLASS = "cn-input-otp flex items-center has-disabled:opacity-50"
|
|
12
|
+
CONTAINER_STYLE = "position: relative; cursor: %<cursor>s; user-select: none; " \
|
|
13
|
+
"-webkit-user-select: none; pointer-events: none"
|
|
14
|
+
DISPLAY_STYLE = "display: contents"
|
|
15
|
+
INPUT_ACTION = T.let([
|
|
16
|
+
"input->#{InputOTP::CONTROLLER}#sync",
|
|
17
|
+
"change->#{InputOTP::CONTROLLER}#sync",
|
|
18
|
+
"focus->#{InputOTP::CONTROLLER}#sync",
|
|
19
|
+
"blur->#{InputOTP::CONTROLLER}#sync",
|
|
20
|
+
"select->#{InputOTP::CONTROLLER}#sync",
|
|
21
|
+
"invalid->#{InputOTP::CONTROLLER}#sync",
|
|
22
|
+
"compositionstart->#{InputOTP::CONTROLLER}#compositionStart",
|
|
23
|
+
"compositionend->#{InputOTP::CONTROLLER}#compositionEnd"
|
|
24
|
+
].join(" ").freeze, String)
|
|
25
|
+
INPUT_STYLE = T.let({
|
|
26
|
+
position: "absolute",
|
|
27
|
+
inset: 0,
|
|
28
|
+
width: "100%",
|
|
29
|
+
height: "100%",
|
|
30
|
+
display: "flex",
|
|
31
|
+
text_align: "left",
|
|
32
|
+
opacity: 1,
|
|
33
|
+
color: "transparent",
|
|
34
|
+
pointer_events: "all",
|
|
35
|
+
background: "transparent",
|
|
36
|
+
caret_color: "transparent",
|
|
37
|
+
border: "0 solid transparent",
|
|
38
|
+
outline: "0 solid transparent",
|
|
39
|
+
box_shadow: "none",
|
|
40
|
+
line_height: 1,
|
|
41
|
+
letter_spacing: "-.5em",
|
|
42
|
+
font_size: "var(--root-height, 16px)",
|
|
43
|
+
font_family: "monospace",
|
|
44
|
+
font_variant_numeric: "tabular-nums"
|
|
45
|
+
}.freeze, T::Hash[Symbol, T.untyped])
|
|
46
|
+
INPUT_OVERLAY_STYLE = "position: absolute; inset: 0; pointer-events: none"
|
|
47
|
+
NOSCRIPT_CSS_FALLBACK = <<~CSS
|
|
48
|
+
[data-input-otp] {
|
|
49
|
+
--nojs-bg: white !important;
|
|
50
|
+
--nojs-fg: black !important;
|
|
51
|
+
|
|
52
|
+
background-color: var(--nojs-bg) !important;
|
|
53
|
+
color: var(--nojs-fg) !important;
|
|
54
|
+
caret-color: var(--nojs-fg) !important;
|
|
55
|
+
letter-spacing: .25em !important;
|
|
56
|
+
text-align: center !important;
|
|
57
|
+
border: 1px solid var(--nojs-fg) !important;
|
|
58
|
+
border-radius: 4px !important;
|
|
59
|
+
width: 100% !important;
|
|
60
|
+
}
|
|
61
|
+
@media (prefers-color-scheme: dark) {
|
|
62
|
+
[data-input-otp] {
|
|
63
|
+
--nojs-bg: black !important;
|
|
64
|
+
--nojs-fg: white !important;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
CSS
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private_constant :Markup
|
|
71
|
+
|
|
72
|
+
sig do
|
|
73
|
+
params(
|
|
74
|
+
length: T.any(Integer, String),
|
|
75
|
+
value: T.nilable(String),
|
|
76
|
+
container_class: T.nilable(String),
|
|
77
|
+
args: T::Hash[Symbol, T.untyped]
|
|
78
|
+
).void.checked(:never)
|
|
79
|
+
end
|
|
80
|
+
def initialize(length: self.class.property_default(:length), value: nil, container_class: nil, **args)
|
|
81
|
+
@length = T.let(normalize_property(:length, length), Integer)
|
|
82
|
+
@value = T.let(value&.slice(0, @length), T.nilable(String))
|
|
83
|
+
@container_class = T.let(container_class, T.nilable(String))
|
|
84
|
+
super(**args)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sig { override.returns(String) }
|
|
88
|
+
def call
|
|
89
|
+
safe_join([noscript_fallback, input_container])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
sig { returns(String) }
|
|
95
|
+
def input_container
|
|
96
|
+
content_tag(
|
|
97
|
+
:div,
|
|
98
|
+
class: container_class,
|
|
99
|
+
style: format(Markup::CONTAINER_STYLE, cursor: disabled? ? "default" : "text"),
|
|
100
|
+
data: { controller: CONTROLLER, input_otp_container: "" }
|
|
101
|
+
) do
|
|
102
|
+
safe_join([decorative_display, input_overlay])
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
sig { returns(String) }
|
|
107
|
+
def decorative_display
|
|
108
|
+
data = T.let({ input_otp_display: "" }, T::Hash[Symbol, T.untyped])
|
|
109
|
+
data[:disabled] = "" if disabled?
|
|
110
|
+
data[:invalid] = "" if invalid?
|
|
111
|
+
|
|
112
|
+
content_tag(:div, style: Markup::DISPLAY_STYLE, aria: { hidden: "true" }, data:) do
|
|
113
|
+
content? ? content : standard_group
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
sig { returns(String) }
|
|
118
|
+
def standard_group
|
|
119
|
+
render(Group.new) do
|
|
120
|
+
safe_join(Array.new(@length) { |index| standard_slot(index) })
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
sig { params(index: Integer).returns(String) }
|
|
125
|
+
def standard_slot(index)
|
|
126
|
+
data = T.let({}, T::Hash[Symbol, T.untyped])
|
|
127
|
+
data[:disabled] = "" if disabled?
|
|
128
|
+
|
|
129
|
+
render(Slot.new(index:, data:, aria: { invalid: invalid?.to_s })) do
|
|
130
|
+
@value.to_s[index].to_s
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
sig { returns(String) }
|
|
135
|
+
def input_overlay
|
|
136
|
+
content_tag(:div, style: Markup::INPUT_OVERLAY_STYLE) { input_element }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
sig { returns(String) }
|
|
140
|
+
def input_element
|
|
141
|
+
attributes = html_attributes
|
|
142
|
+
attributes[:type] = "text"
|
|
143
|
+
attributes[:inputmode] = "numeric" unless attributes.key?(:inputmode)
|
|
144
|
+
attributes[:maxlength] = @length
|
|
145
|
+
attributes[:autocomplete] = "one-time-code" unless attributes.key?(:autocomplete)
|
|
146
|
+
attributes[:spellcheck] = false unless attributes.key?(:spellcheck)
|
|
147
|
+
attributes[:value] = @value unless @value.nil?
|
|
148
|
+
merge_style(attributes, Markup::INPUT_STYLE)
|
|
149
|
+
merge_input_data(attributes)
|
|
150
|
+
void_tag("input", **attributes)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped]).void }
|
|
154
|
+
def merge_input_data(attributes)
|
|
155
|
+
data = T.cast(attributes[:data], T.nilable(T::Hash[Symbol, T.untyped])) || {}
|
|
156
|
+
user_action = data[:action]
|
|
157
|
+
attributes[:data] = data.merge(
|
|
158
|
+
input_otp: "",
|
|
159
|
+
action: [Markup::INPUT_ACTION, user_action].compact.join(" ")
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sig { returns(String) }
|
|
164
|
+
def noscript_fallback
|
|
165
|
+
content_tag(:noscript) do
|
|
166
|
+
content_tag(:style) { raw(Markup::NOSCRIPT_CSS_FALLBACK) }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
sig { returns(String) }
|
|
171
|
+
def container_class
|
|
172
|
+
ShadcnViewComponents::Classes::MERGER.merge(
|
|
173
|
+
[Markup::CONTAINER_CLASS, @container_class].compact.join(" ")
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
sig { returns(T::Boolean) }
|
|
178
|
+
def disabled?
|
|
179
|
+
!@html_args[:disabled].nil? && @html_args[:disabled] != false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
sig { returns(T::Boolean) }
|
|
183
|
+
def invalid?
|
|
184
|
+
aria = T.cast(@html_args[:aria], T.nilable(T::Hash[Symbol, T.untyped])) || {}
|
|
185
|
+
aria[:invalid] == true || aria[:invalid].to_s == "true"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Group < BaseComponent
|
|
189
|
+
# div
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
class Slot < BaseComponent
|
|
193
|
+
CARET_WRAPPER_CLASS = "pointer-events-none absolute inset-0 flex items-center justify-center"
|
|
194
|
+
CARET_CLASS = "h-4 w-px animate-caret-blink bg-foreground duration-1000"
|
|
195
|
+
|
|
196
|
+
private_constant :CARET_WRAPPER_CLASS, :CARET_CLASS
|
|
197
|
+
|
|
198
|
+
sig { params(index: T.any(Integer, String), args: T::Hash[Symbol, T.untyped]).void.checked(:never) }
|
|
199
|
+
def initialize(index:, **args)
|
|
200
|
+
@index = T.let(normalize_property(:index, index), Integer)
|
|
201
|
+
super(**args)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
sig { override.returns(String) }
|
|
205
|
+
def call
|
|
206
|
+
content_tag(tag, **html_attributes) do
|
|
207
|
+
safe_join([character, fake_caret])
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
214
|
+
def html_attributes
|
|
215
|
+
super.tap do |attributes|
|
|
216
|
+
data = T.cast(attributes[:data], T::Hash[Symbol, T.untyped])
|
|
217
|
+
data[:index] = @index
|
|
218
|
+
data[:active] = "false" unless data.key?(:active)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
sig { returns(String) }
|
|
223
|
+
def character
|
|
224
|
+
content_tag(:span, data: { slot: "input-otp-character" }) { content }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
sig { returns(String) }
|
|
228
|
+
def fake_caret
|
|
229
|
+
content_tag(
|
|
230
|
+
:div,
|
|
231
|
+
class: CARET_WRAPPER_CLASS,
|
|
232
|
+
hidden: true,
|
|
233
|
+
aria: { hidden: "true" },
|
|
234
|
+
data: { slot: "input-otp-caret" }
|
|
235
|
+
) do
|
|
236
|
+
content_tag(:div, class: CARET_CLASS) { "".html_safe }
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class Separator < BaseComponent
|
|
242
|
+
sig { override.returns(String) }
|
|
243
|
+
def call
|
|
244
|
+
content_tag(tag, **html_attributes) { minus_icon }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
|
|
249
|
+
sig { returns(String) }
|
|
250
|
+
def minus_icon
|
|
251
|
+
content_tag(
|
|
252
|
+
:svg,
|
|
253
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
254
|
+
viewBox: "0 0 24 24",
|
|
255
|
+
fill: "none",
|
|
256
|
+
stroke: "currentColor",
|
|
257
|
+
"stroke-width": "2",
|
|
258
|
+
"stroke-linecap": "round",
|
|
259
|
+
"stroke-linejoin": "round",
|
|
260
|
+
aria: { hidden: "true" }
|
|
261
|
+
) { raw(%(<path d="M5 12h14"/>)) }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class Item < BaseComponent
|
|
6
|
+
sig do
|
|
7
|
+
params(
|
|
8
|
+
variant: T.any(Symbol, String),
|
|
9
|
+
size: T.any(Symbol, String),
|
|
10
|
+
args: T::Hash[Symbol, T.untyped]
|
|
11
|
+
).void.checked(:never)
|
|
12
|
+
end
|
|
13
|
+
def initialize(variant: ShadcnViewComponents::Contracts::Item::DEFAULTS.fetch(:variant),
|
|
14
|
+
size: ShadcnViewComponents::Contracts::Item::DEFAULTS.fetch(:size), **args)
|
|
15
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
16
|
+
@size = T.let(normalize_option(:size, size), Symbol)
|
|
17
|
+
super(**args)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
sig { returns(Symbol) }
|
|
21
|
+
attr_reader :variant
|
|
22
|
+
|
|
23
|
+
sig { returns(Symbol) }
|
|
24
|
+
attr_reader :size
|
|
25
|
+
|
|
26
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
27
|
+
def variant_options
|
|
28
|
+
{ variant: @variant, size: @size }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# upstream useRender の state は data-variant / data-size として DOM に現れる
|
|
32
|
+
# (ItemGroup の has-data-[size=sm]:gap-2.5 等がこれを参照する)
|
|
33
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
34
|
+
def contract_data_attributes
|
|
35
|
+
super.merge(variant: @variant, size: @size)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Media < BaseComponent
|
|
39
|
+
sig do
|
|
40
|
+
params(
|
|
41
|
+
variant: T.any(Symbol, String),
|
|
42
|
+
args: T::Hash[Symbol, T.untyped]
|
|
43
|
+
).void.checked(:never)
|
|
44
|
+
end
|
|
45
|
+
def initialize(variant: ShadcnViewComponents::Contracts::Item::Media::DEFAULTS.fetch(:variant), **args)
|
|
46
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
47
|
+
super(**args)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { returns(Symbol) }
|
|
51
|
+
attr_reader :variant
|
|
52
|
+
|
|
53
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
54
|
+
def variant_options
|
|
55
|
+
{ variant: @variant }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
59
|
+
def contract_data_attributes
|
|
60
|
+
super.merge(variant: @variant)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# upstream は Separator(別アイテム)をラップして "my-0" を重ねる構成。
|
|
65
|
+
# 抽出器は単一アイテム内のクラスしか事前解決しないため、item側の契約には
|
|
66
|
+
# "my-0" のみが記録される。描画は upstream のDOMと同じく Separator のクラス群 +
|
|
67
|
+
# "my-0" とし、クラス検証は contains モードで行う(spec/conformance/allowances.yml 参照)
|
|
68
|
+
class Separator < BaseComponent
|
|
69
|
+
sig { override.returns(String) }
|
|
70
|
+
def call
|
|
71
|
+
content_tag(
|
|
72
|
+
:div,
|
|
73
|
+
role: "separator",
|
|
74
|
+
data: { slot: contract_root_slot[:name], orientation: "horizontal" },
|
|
75
|
+
aria: { orientation: "horizontal" },
|
|
76
|
+
class: separator_class
|
|
77
|
+
) { content }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
sig { returns(String) }
|
|
83
|
+
def separator_class
|
|
84
|
+
# base-nova の item-separator は Separator(別アイテム)のクラスの上に
|
|
85
|
+
# item 側の my-2 を合成する(item契約には my-2 のみ記録される)。
|
|
86
|
+
# h-px / bg-border は Separator 契約の data-horizontal: バリアントが担当し、
|
|
87
|
+
# これが無いと separator の高さが 0 になる
|
|
88
|
+
ShadcnViewComponents::Classes::MERGER.merge(
|
|
89
|
+
"#{ShadcnViewComponents::Classes.resolve(:separator)} #{self.class.classes}"
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Content < BaseComponent; end
|
|
95
|
+
|
|
96
|
+
class Actions < BaseComponent; end
|
|
97
|
+
|
|
98
|
+
class Group < BaseComponent; end
|
|
99
|
+
|
|
100
|
+
class Title < BaseComponent; end
|
|
101
|
+
|
|
102
|
+
class Description < BaseComponent; end
|
|
103
|
+
|
|
104
|
+
class Header < BaseComponent; end
|
|
105
|
+
|
|
106
|
+
class Footer < BaseComponent; end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class Marker < BaseComponent
|
|
6
|
+
# 省略時の既定値は契約の defaults から読む(リテラルで二重管理しない)
|
|
7
|
+
sig do
|
|
8
|
+
params(
|
|
9
|
+
variant: T.any(Symbol, String),
|
|
10
|
+
args: T::Hash[Symbol, T.untyped]
|
|
11
|
+
).void.checked(:never)
|
|
12
|
+
end
|
|
13
|
+
def initialize(variant: ShadcnViewComponents::Contracts::Marker::DEFAULTS.fetch(:variant), **args)
|
|
14
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
15
|
+
super(**args)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
sig { returns(Symbol) }
|
|
19
|
+
attr_reader :variant
|
|
20
|
+
|
|
21
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
22
|
+
def variant_options
|
|
23
|
+
{ variant: @variant }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
27
|
+
def contract_data_attributes
|
|
28
|
+
super.merge(variant: @variant)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Content < BaseComponent
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Icon < BaseComponent
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# メニューバー(10-roadmap Phase 3「menubar = 横断」)。
|
|
6
|
+
# 横並びのトリガー群と複数メニューをmenubar_controllerで一対一に管理する。
|
|
7
|
+
# 部品の描画はDropdownMenuを継承するが、状態とキーボード操作は共有しない。
|
|
8
|
+
# JS無効時フォールバック: Readable
|
|
9
|
+
class Menubar < DropdownMenu
|
|
10
|
+
CONTROLLER = "shadcn--menubar"
|
|
11
|
+
|
|
12
|
+
module ControllerIdentifier
|
|
13
|
+
extend T::Sig
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
sig { returns(String) }
|
|
18
|
+
def controller_identifier
|
|
19
|
+
Menubar::CONTROLLER
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private_constant :ControllerIdentifier
|
|
24
|
+
|
|
25
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
26
|
+
def contract_data_attributes
|
|
27
|
+
super.merge(controller: CONTROLLER)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
31
|
+
def html_attributes
|
|
32
|
+
attributes = super
|
|
33
|
+
attributes[:role] = "menubar"
|
|
34
|
+
merge_nested(attributes, :aria, { orientation: "horizontal" })
|
|
35
|
+
attributes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Menu < BaseComponent
|
|
39
|
+
# トリガー+コンテンツのスコープ(div)
|
|
40
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
41
|
+
def html_attributes
|
|
42
|
+
super.tap { |attributes| attributes[:role] = "none" }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Trigger < DropdownMenu::Trigger
|
|
47
|
+
include ControllerIdentifier
|
|
48
|
+
|
|
49
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
50
|
+
def html_attributes
|
|
51
|
+
super.tap { |attributes| attributes[:role] = "menuitem" }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Portal < DropdownMenu::Portal
|
|
56
|
+
# 搬送のみの機能要素の実体化
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Content < DropdownMenu::Content
|
|
60
|
+
FLOATING_POSITION_DEFAULTS = T.let(
|
|
61
|
+
{ side: :bottom, align: :start, side_offset: 8, align_offset: -4, collision_padding: 5 }.freeze,
|
|
62
|
+
T::Hash[Symbol, T.untyped]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# popover=auto の ARIA menu
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Group < DropdownMenu::Group; end
|
|
69
|
+
|
|
70
|
+
class Label < DropdownMenu::Label; end
|
|
71
|
+
|
|
72
|
+
class Item < DropdownMenu::Item
|
|
73
|
+
include ControllerIdentifier
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class CheckboxItem < DropdownMenu::CheckboxItem
|
|
77
|
+
include ControllerIdentifier
|
|
78
|
+
|
|
79
|
+
# 契約に indicator スロットが無いため data-slot 無しで描く
|
|
80
|
+
sig { returns(T.nilable(String)) }
|
|
81
|
+
def indicator_slot_name
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class RadioGroup < DropdownMenu::RadioGroup; end
|
|
87
|
+
|
|
88
|
+
class RadioItem < DropdownMenu::RadioItem
|
|
89
|
+
include ControllerIdentifier
|
|
90
|
+
|
|
91
|
+
# 契約に indicator スロットが無いため data-slot 無しで描く
|
|
92
|
+
sig { returns(T.nilable(String)) }
|
|
93
|
+
def indicator_slot_name
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class Separator < DropdownMenu::Separator; end
|
|
99
|
+
|
|
100
|
+
class Shortcut < DropdownMenu::Shortcut; end
|
|
101
|
+
|
|
102
|
+
class Sub < DropdownMenu::Sub; end
|
|
103
|
+
|
|
104
|
+
class SubTrigger < DropdownMenu::SubTrigger
|
|
105
|
+
include ControllerIdentifier
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class SubContent < DropdownMenu::SubContent; end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# AIチャット等のメッセージ行(Phase 4)。静的な表示構造
|
|
6
|
+
class Message < BaseComponent
|
|
7
|
+
# div
|
|
8
|
+
|
|
9
|
+
class Group < BaseComponent
|
|
10
|
+
# div
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Avatar < BaseComponent
|
|
14
|
+
# div
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Content < BaseComponent
|
|
18
|
+
# div
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Footer < BaseComponent
|
|
22
|
+
# div
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Header < BaseComponent
|
|
26
|
+
# div
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# メッセージのスクロール領域(Phase 4)。一番下への追従・ボタンによる
|
|
6
|
+
# スクロールは message_scroller_controller が担う
|
|
7
|
+
# JS無効時フォールバック: Graceful(縦スクロールはネイティブ)
|
|
8
|
+
class MessageScroller < BaseComponent
|
|
9
|
+
CONTROLLER = "shadcn--message-scroller"
|
|
10
|
+
|
|
11
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
12
|
+
def contract_data_attributes
|
|
13
|
+
super.merge(controller: CONTROLLER)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Provider < BaseComponent
|
|
17
|
+
# 状態のスコープ(div)。子をそのまま並べる
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Viewport < BaseComponent
|
|
21
|
+
# div(スクロール対象)
|
|
22
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
23
|
+
def html_attributes
|
|
24
|
+
super.tap { |attributes| attributes[:tabindex] = "0" }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Content < BaseComponent
|
|
29
|
+
# div
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Item < BaseComponent
|
|
33
|
+
# div
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Button < BaseComponent
|
|
37
|
+
# 一番下へ戻るボタン
|
|
38
|
+
sig { override.returns(String) }
|
|
39
|
+
def default_tag
|
|
40
|
+
"button"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
44
|
+
def html_attributes
|
|
45
|
+
attributes = super
|
|
46
|
+
attributes[:type] = "button" unless attributes.key?(:type)
|
|
47
|
+
merge_nested(attributes, :data, { action: "#{CONTROLLER}#scrollToBottom" })
|
|
48
|
+
attributes
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# Checkbox / Radio / Switch が共有する、ネイティブ input の checked 状態を
|
|
6
|
+
# upstream 契約の data 属性へ投影するための内部実装。
|
|
7
|
+
module NativeCheckedState
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
CONTROLLER = "shadcn--checked-state"
|
|
11
|
+
CHANGE_ACTION = T.let("change->#{CONTROLLER}#sync".freeze, String)
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped], checked: T::Boolean).void }
|
|
16
|
+
def merge_native_checked_state(attributes, checked:)
|
|
17
|
+
attributes[:data] = native_checked_data_attributes(attributes[:data], checked:)
|
|
18
|
+
normalize_explicit_aria_checked(attributes, checked:)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
sig { params(raw_data: T.untyped, checked: T::Boolean).returns(T::Hash[T.untyped, T.untyped]) }
|
|
22
|
+
def native_checked_data_attributes(raw_data, checked:)
|
|
23
|
+
data = T.cast(raw_data, T.nilable(T::Hash[T.untyped, T.untyped])) || {}
|
|
24
|
+
data = data.dup
|
|
25
|
+
user_controller = delete_native_data_value(data, :controller)
|
|
26
|
+
user_action = delete_native_data_value(data, :action)
|
|
27
|
+
delete_native_data_value(data, :checked)
|
|
28
|
+
delete_native_data_value(data, :unchecked)
|
|
29
|
+
data[:controller] = compose_checked_state_tokens(CONTROLLER, user_controller)
|
|
30
|
+
data[:action] = compose_checked_state_tokens(CHANGE_ACTION, user_action)
|
|
31
|
+
data[checked ? :checked : :unchecked] = ""
|
|
32
|
+
data
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { params(data: T::Hash[T.untyped, T.untyped], key: Symbol).returns(T.untyped) }
|
|
36
|
+
def delete_native_data_value(data, key)
|
|
37
|
+
symbol_value = data.delete(key)
|
|
38
|
+
string_value = data.delete(key.to_s)
|
|
39
|
+
symbol_value || string_value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { params(html_args: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
|
|
43
|
+
def native_checked?(html_args)
|
|
44
|
+
value = html_args[:checked]
|
|
45
|
+
!value.nil? && value != false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
sig { params(checked: T::Boolean).returns(T::Hash[Symbol, String]) }
|
|
49
|
+
def native_checked_data(checked:)
|
|
50
|
+
checked ? { checked: "" } : { unchecked: "" }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig { params(tokens: T.untyped).returns(String) }
|
|
54
|
+
def compose_checked_state_tokens(*tokens)
|
|
55
|
+
tokens.compact.flat_map { |token| token.to_s.split }.uniq.join(" ")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped], checked: T::Boolean).void }
|
|
59
|
+
def normalize_explicit_aria_checked(attributes, checked:)
|
|
60
|
+
aria = T.cast(attributes[:aria], T.nilable(T::Hash[T.untyped, T.untyped]))
|
|
61
|
+
if aria&.key?(:checked) || aria&.key?("checked")
|
|
62
|
+
normalized = aria.dup
|
|
63
|
+
normalized.delete("checked")
|
|
64
|
+
normalized[:checked] = checked.to_s
|
|
65
|
+
attributes[:aria] = normalized
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
key = :"aria-checked"
|
|
69
|
+
attributes[key] = checked.to_s if attributes.key?(key)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private_constant :NativeCheckedState
|
|
74
|
+
end
|