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,79 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class Switch < BaseComponent
|
|
6
|
+
include NativeCheckedState
|
|
7
|
+
|
|
8
|
+
NATIVE_STATE_CLASS = "not-checked:bg-input dark:not-checked:bg-input/80 checked:bg-primary disabled:cursor-not-allowed disabled:opacity-50"
|
|
9
|
+
# 契約タグは SwitchPrimitive.Root。ネイティブな input[type=checkbox][role=switch] として
|
|
10
|
+
# 描く(JS無しで動作 — 05-stimulus-hotwire §3)。ツマミは input の兄弟に置き、
|
|
11
|
+
# peer-checked: で移動させる(契約の data-[state] ユーティリティを peer-checked 系に
|
|
12
|
+
# キーし直した装飾を追加している)
|
|
13
|
+
sig { override.returns(String) }
|
|
14
|
+
def default_tag
|
|
15
|
+
"input"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# upstream は size(default/sm)を data-size 属性として描画する
|
|
19
|
+
sig do
|
|
20
|
+
params(
|
|
21
|
+
size: T.any(Symbol, String),
|
|
22
|
+
args: T::Hash[Symbol, T.untyped]
|
|
23
|
+
).void.checked(:never)
|
|
24
|
+
end
|
|
25
|
+
def initialize(size: self.class.property_default(:size), **args)
|
|
26
|
+
@size = T.let(normalize_property(:size, size), String)
|
|
27
|
+
super(**args)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { returns(String) }
|
|
31
|
+
attr_reader :size
|
|
32
|
+
|
|
33
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
34
|
+
def contract_data_attributes
|
|
35
|
+
super.merge(size: @size)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { override.returns(String) }
|
|
39
|
+
def call
|
|
40
|
+
content_tag(:span, class: "relative inline-flex") do
|
|
41
|
+
safe_join([input_element, thumb_element])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
sig { returns(String) }
|
|
48
|
+
def input_element
|
|
49
|
+
attributes = html_attributes.merge(type: "checkbox", role: "switch")
|
|
50
|
+
# ネイティブチェックボックスのウィジェット描画(appearance:auto)はCSSの
|
|
51
|
+
# 背景/角丸の上にOS標準の箱を塗る(dark時は白箱として視覚差になる)ため消す。
|
|
52
|
+
# 契約の data-[state] 系背景クラスを素の要素に効かせるために必要
|
|
53
|
+
attributes[:class] = [attributes[:class], "appearance-none", NATIVE_STATE_CLASS].compact.join(" ")
|
|
54
|
+
merge_native_checked_state(attributes, checked: native_checked?(@html_args))
|
|
55
|
+
void_tag("input", **attributes)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { returns(String) }
|
|
59
|
+
def thumb_element
|
|
60
|
+
thumb_class = T.cast(contract_slot("switch-thumb").dig(:static_attributes, :class), T.nilable(String))
|
|
61
|
+
# 契約のツマミサイズ(group-data-[size]/switch:size-*)はルートをgroupとする
|
|
62
|
+
# 子孫向けユーティリティのため、ネイティブinput構成ではツマミが兄弟になるので
|
|
63
|
+
# 発火しない。sizeはコンポーネント側で明示する(契約のdata-size値と同じ)
|
|
64
|
+
size_class = @size == "sm" ? "size-3" : "size-4"
|
|
65
|
+
decoration = [
|
|
66
|
+
"pointer-events-none absolute left-0.5 top-1/2 -translate-y-1/2 translate-x-0",
|
|
67
|
+
"peer-checked:translate-x-[calc(100%-2px)] peer-not-checked:translate-x-0",
|
|
68
|
+
"dark:peer-checked:bg-primary-foreground dark:peer-not-checked:bg-foreground transition-transform",
|
|
69
|
+
size_class
|
|
70
|
+
].join(" ")
|
|
71
|
+
content_tag(
|
|
72
|
+
:span,
|
|
73
|
+
data: { slot: "switch-thumb" }.merge(native_checked_data(checked: native_checked?(@html_args))),
|
|
74
|
+
aria: { hidden: "true" },
|
|
75
|
+
class: [thumb_class, decoration].compact.join(" ")
|
|
76
|
+
) { "".html_safe }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class Table < BaseComponent
|
|
6
|
+
# upstream は table を overflow-x-auto のラッパーdivで挟む構造。
|
|
7
|
+
# 契約のルート(table-container)は cn を経ない静的クラス(static_attributes["class"])を持ち、
|
|
8
|
+
# 契約クラス(combinations)は内側の table 要素が持つ(CLASSES_SLOT = "table")
|
|
9
|
+
sig { override.returns(String) }
|
|
10
|
+
def call
|
|
11
|
+
content_tag(:div, class: container_class, data: { slot: contract_root_slot[:name] }) do
|
|
12
|
+
content_tag(:table, **table_attributes) { content }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
sig { returns(T.nilable(String)) }
|
|
19
|
+
def container_class
|
|
20
|
+
T.cast(contract_root_slot[:static_attributes], T::Hash[Symbol, T.untyped])[:class]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 内側の table に契約クラスを適用する。ラッパーの静的クラスは混ぜない
|
|
24
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
25
|
+
def table_attributes
|
|
26
|
+
attributes = @html_args.merge(class: self.class.classes(extra: @user_class))
|
|
27
|
+
merge_nested(attributes, :data, { slot: self.class.contract.const_get(:CLASSES_SLOT) })
|
|
28
|
+
merge_nested(attributes, :aria, {})
|
|
29
|
+
attributes
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Header < BaseComponent; end
|
|
33
|
+
|
|
34
|
+
class Body < BaseComponent; end
|
|
35
|
+
|
|
36
|
+
class Footer < BaseComponent; end
|
|
37
|
+
|
|
38
|
+
class Head < BaseComponent; end
|
|
39
|
+
|
|
40
|
+
class Row < BaseComponent; end
|
|
41
|
+
|
|
42
|
+
class Cell < BaseComponent; end
|
|
43
|
+
|
|
44
|
+
class Caption < BaseComponent; end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# ARIA tabs パターン + roving tabindex(10-roadmap Phase 2 / 05 §4)。
|
|
6
|
+
# 選択状態は data-state で持ち、パネルの表示切替もコントローラが行う
|
|
7
|
+
# JS無効時フォールバック: Readable(SSRされた全パネルが展開された状態で出力される)
|
|
8
|
+
class Tabs < BaseComponent
|
|
9
|
+
CONTROLLER = "shadcn--tabs"
|
|
10
|
+
|
|
11
|
+
sig { params(orientation: T.any(Symbol, String), args: T::Hash[Symbol, T.untyped]).void.checked(:never) }
|
|
12
|
+
def initialize(orientation: self.class.property_default(:orientation), **args)
|
|
13
|
+
assign_accessibility_root_id(args, prefix: "tabs")
|
|
14
|
+
@orientation = T.let(normalize_property(:orientation, orientation), String)
|
|
15
|
+
super(**args)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
19
|
+
def contract_data_attributes
|
|
20
|
+
super.merge(
|
|
21
|
+
controller: CONTROLLER,
|
|
22
|
+
orientation: @orientation
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class List < BaseComponent
|
|
27
|
+
sig do
|
|
28
|
+
params(
|
|
29
|
+
variant: T.any(Symbol, String),
|
|
30
|
+
args: T::Hash[Symbol, T.untyped]
|
|
31
|
+
).void.checked(:never)
|
|
32
|
+
end
|
|
33
|
+
def initialize(variant: ShadcnViewComponents::Contracts::Tabs::List::DEFAULTS.fetch(:variant), **args)
|
|
34
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
35
|
+
super(**args)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
39
|
+
def variant_options
|
|
40
|
+
{ variant: @variant }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
44
|
+
def html_attributes
|
|
45
|
+
attributes = super
|
|
46
|
+
merge_nested(attributes, :data, { variant: @variant })
|
|
47
|
+
attributes[:role] = "tablist"
|
|
48
|
+
attributes
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Trigger < BaseComponent
|
|
53
|
+
CONTROLLER = "shadcn--tabs"
|
|
54
|
+
|
|
55
|
+
sig do
|
|
56
|
+
params(
|
|
57
|
+
value: String,
|
|
58
|
+
active: T::Boolean,
|
|
59
|
+
args: T::Hash[Symbol, T.untyped]
|
|
60
|
+
).void.checked(:never)
|
|
61
|
+
end
|
|
62
|
+
def initialize(value:, active: false, **args)
|
|
63
|
+
raw_value = T.let(T.unsafe(value), T.untyped)
|
|
64
|
+
raise ArgumentError, "value must be a non-empty String" unless raw_value.is_a?(String) && !raw_value.strip.empty?
|
|
65
|
+
|
|
66
|
+
@value = T.let(value, String)
|
|
67
|
+
@active = active
|
|
68
|
+
super(**args)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig { override.returns(String) }
|
|
72
|
+
def default_tag
|
|
73
|
+
"button"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
77
|
+
def html_attributes
|
|
78
|
+
attributes = super
|
|
79
|
+
# base-nova は選択状態を data-active(属性の存在)で表現する
|
|
80
|
+
state_attributes = @active ? { active: "" } : {}
|
|
81
|
+
merge_nested(attributes, :data, {
|
|
82
|
+
value: @value
|
|
83
|
+
}.compact.merge(state_attributes))
|
|
84
|
+
merge_nested(attributes, :aria, { selected: @active.to_s })
|
|
85
|
+
attributes[:role] = "tab"
|
|
86
|
+
attributes[:tabindex] = @active ? 0 : -1
|
|
87
|
+
attributes[:type] = "button" unless attributes.key?(:type) || tag == "a"
|
|
88
|
+
attributes
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# contract_data_attributes の slot に action を足す(ルートが button のため)
|
|
92
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
93
|
+
def contract_data_attributes
|
|
94
|
+
super.merge(
|
|
95
|
+
action: "click->#{CONTROLLER}#select keydown->#{CONTROLLER}#navigate"
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class Content < BaseComponent
|
|
101
|
+
sig { params(value: String, args: T::Hash[Symbol, T.untyped]).void.checked(:never) }
|
|
102
|
+
def initialize(value:, **args)
|
|
103
|
+
raw_value = T.let(T.unsafe(value), T.untyped)
|
|
104
|
+
raise ArgumentError, "value must be a non-empty String" unless raw_value.is_a?(String) && !raw_value.strip.empty?
|
|
105
|
+
|
|
106
|
+
@value = T.let(value, String)
|
|
107
|
+
super(**args)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
111
|
+
def html_attributes
|
|
112
|
+
attributes = super
|
|
113
|
+
merge_nested(attributes, :data, { value: @value }.compact)
|
|
114
|
+
attributes[:role] = "tabpanel"
|
|
115
|
+
attributes
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class Toggle < BaseComponent
|
|
6
|
+
# 契約タグは TogglePrimitive.Root。button + data-state + 小コントローラ
|
|
7
|
+
# (shadcn--toggle)で開閉する(05-stimulus-hotwire §3「toggle = button + data-state」)
|
|
8
|
+
# JS無効時フォールバック: Readable(押下状態は変わらないが内容の読み取りに支障無し)
|
|
9
|
+
CONTROLLER = "shadcn--toggle"
|
|
10
|
+
|
|
11
|
+
# 契約タグは TogglePrimitive.Root。ネイティブな button として描く
|
|
12
|
+
sig { override.returns(String) }
|
|
13
|
+
def default_tag
|
|
14
|
+
"button"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# state(on/off)は初期状態。以降はコントローラが data-state / aria-pressed を書き換える
|
|
18
|
+
sig do
|
|
19
|
+
params(
|
|
20
|
+
variant: T.any(Symbol, String),
|
|
21
|
+
size: T.any(Symbol, String),
|
|
22
|
+
state: T.any(Symbol, String),
|
|
23
|
+
args: T::Hash[Symbol, T.untyped]
|
|
24
|
+
).void.checked(:never)
|
|
25
|
+
end
|
|
26
|
+
def initialize(variant: ShadcnViewComponents::Contracts::Toggle::DEFAULTS.fetch(:variant),
|
|
27
|
+
size: ShadcnViewComponents::Contracts::Toggle::DEFAULTS.fetch(:size),
|
|
28
|
+
state: self.class.property_default(:state), **args)
|
|
29
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
30
|
+
@size = T.let(normalize_option(:size, size), Symbol)
|
|
31
|
+
@state = T.let(normalize_property(:state, state), String)
|
|
32
|
+
super(**args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { returns(Symbol) }
|
|
36
|
+
attr_reader :variant
|
|
37
|
+
|
|
38
|
+
sig { returns(Symbol) }
|
|
39
|
+
attr_reader :size
|
|
40
|
+
|
|
41
|
+
sig { returns(String) }
|
|
42
|
+
attr_reader :state
|
|
43
|
+
|
|
44
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
45
|
+
def variant_options
|
|
46
|
+
{ variant: @variant, size: @size }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
50
|
+
def contract_data_attributes
|
|
51
|
+
super.merge(
|
|
52
|
+
controller: CONTROLLER,
|
|
53
|
+
state: @state,
|
|
54
|
+
action: "#{CONTROLLER}#toggle"
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
59
|
+
def html_attributes
|
|
60
|
+
attributes = super
|
|
61
|
+
merge_nested(attributes, :aria, { pressed: (@state == "on").to_s })
|
|
62
|
+
attributes[:type] = "button" unless @html_args.key?(:type) || attributes.key?(:type)
|
|
63
|
+
attributes
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
class ToggleGroup < BaseComponent
|
|
6
|
+
# JS無効時フォールバック: Readable(選択状態は変わらないが内容の読み取りに支障無し)
|
|
7
|
+
|
|
8
|
+
# 契約タグは ToggleGroupPrimitive.Root。排他制御は shadcn--toggle-group コントローラが担う
|
|
9
|
+
CONTROLLER = "shadcn--toggle-group"
|
|
10
|
+
|
|
11
|
+
# type: :single は常に1つのみ on(コントローラが排他処理)。spacing は --gap CSS変数。
|
|
12
|
+
# variant/size は upstream と同じくグループの data 属性に出力する
|
|
13
|
+
# (Item 側の装飾は group-data セレクタと Item 自身の data 属性で効く)
|
|
14
|
+
sig do
|
|
15
|
+
params(
|
|
16
|
+
type: T.any(Symbol, String),
|
|
17
|
+
spacing: T.any(Integer, Float, String),
|
|
18
|
+
variant: T.nilable(T.any(Symbol, String)),
|
|
19
|
+
size: T.nilable(T.any(Symbol, String)),
|
|
20
|
+
args: T::Hash[Symbol, T.untyped]
|
|
21
|
+
).void.checked(:never)
|
|
22
|
+
end
|
|
23
|
+
def initialize(type: self.class.property_default(:type),
|
|
24
|
+
spacing: self.class.property_default(:spacing), variant: nil, size: nil, **args)
|
|
25
|
+
@group_type = T.let(normalize_property(:type, type), String)
|
|
26
|
+
@spacing = T.let(normalize_property(:spacing, spacing), T.any(Integer, Float))
|
|
27
|
+
# グループ自身の契約に軸は無いため、Item の許容値で検証する
|
|
28
|
+
@group_variant = T.let(variant.nil? ? nil : normalize_group_option(variant, :variant), T.nilable(Symbol))
|
|
29
|
+
@group_size = T.let(size.nil? ? nil : normalize_group_option(size, :size), T.nilable(Symbol))
|
|
30
|
+
super(**args)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { returns(String) }
|
|
34
|
+
attr_reader :group_type
|
|
35
|
+
|
|
36
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
37
|
+
def contract_data_attributes
|
|
38
|
+
attributes = super.merge(
|
|
39
|
+
controller: CONTROLLER,
|
|
40
|
+
type: @group_type
|
|
41
|
+
)
|
|
42
|
+
# upstream の Root と同じ data 属性(未指定のものは描かない)
|
|
43
|
+
attributes[:variant] = @group_variant if @group_variant
|
|
44
|
+
attributes[:size] = @group_size if @group_size
|
|
45
|
+
attributes[:spacing] = @spacing.to_s
|
|
46
|
+
attributes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
50
|
+
def html_attributes
|
|
51
|
+
attributes = super
|
|
52
|
+
merge_style(attributes, { "--gap": @spacing.to_s })
|
|
53
|
+
attributes
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# グループの variant/size を Item の許容値で fail-fast 検証する
|
|
59
|
+
sig { params(value: T.untyped, prop: Symbol).returns(Symbol) }
|
|
60
|
+
def normalize_group_option(value, prop)
|
|
61
|
+
ShadcnViewComponents::Classes.normalize_option(:"toggle_group/item", prop, value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Item < BaseComponent
|
|
65
|
+
# 契約タグは ToggleGroupPrimitive.Item。ネイティブな button として描く。
|
|
66
|
+
# 開閉状態は親の shadcn--toggle-group コントローラが管理する(data-action で委譲)
|
|
67
|
+
sig { override.returns(String) }
|
|
68
|
+
def default_tag
|
|
69
|
+
"button"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# variant/size はupstreamではグループのcontextから受け取る。Ruby側では
|
|
73
|
+
# 利用者が各Itemに指定する(規約: 省略時は契約の既定値)。
|
|
74
|
+
# spacing はグループと同じ既定値2を使い、data属性に出力する
|
|
75
|
+
sig do
|
|
76
|
+
params(
|
|
77
|
+
variant: T.any(Symbol, String),
|
|
78
|
+
size: T.any(Symbol, String),
|
|
79
|
+
state: T.any(Symbol, String),
|
|
80
|
+
spacing: T.any(Integer, Float, String),
|
|
81
|
+
args: T::Hash[Symbol, T.untyped]
|
|
82
|
+
).void.checked(:never)
|
|
83
|
+
end
|
|
84
|
+
def initialize(variant: ShadcnViewComponents::Contracts::ToggleGroup::Item::DEFAULTS.fetch(:variant),
|
|
85
|
+
size: ShadcnViewComponents::Contracts::ToggleGroup::Item::DEFAULTS.fetch(:size),
|
|
86
|
+
state: self.class.property_default(:state),
|
|
87
|
+
spacing: self.class.property_default(:spacing), **args)
|
|
88
|
+
@variant = T.let(normalize_option(:variant, variant), Symbol)
|
|
89
|
+
@size = T.let(normalize_option(:size, size), Symbol)
|
|
90
|
+
@state = T.let(normalize_property(:state, state), String)
|
|
91
|
+
@spacing = T.let(normalize_property(:spacing, spacing), T.any(Integer, Float))
|
|
92
|
+
super(**args)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
sig { returns(Symbol) }
|
|
96
|
+
attr_reader :variant
|
|
97
|
+
|
|
98
|
+
sig { returns(Symbol) }
|
|
99
|
+
attr_reader :size
|
|
100
|
+
|
|
101
|
+
sig { override.returns(T::Hash[Symbol, VariantOption]) }
|
|
102
|
+
def variant_options
|
|
103
|
+
{ variant: @variant, size: @size }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# upstream の Item と同じ data 属性(spacing の角丸・枠線制御はここから効く)
|
|
107
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
108
|
+
def contract_data_attributes
|
|
109
|
+
super.merge(
|
|
110
|
+
state: @state,
|
|
111
|
+
variant: @variant,
|
|
112
|
+
size: @size,
|
|
113
|
+
spacing: @spacing.to_s,
|
|
114
|
+
action: "#{ToggleGroup::CONTROLLER}#toggleItem"
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
119
|
+
def html_attributes
|
|
120
|
+
attributes = super
|
|
121
|
+
merge_nested(attributes, :aria, { pressed: (@state == "on").to_s })
|
|
122
|
+
attributes[:type] = "button" unless @html_args.key?(:type) || attributes.key?(:type)
|
|
123
|
+
attributes
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Shadcn
|
|
5
|
+
# 遅延制御付きツールチップ(05 §3「tooltip = Popover API + 遅延制御」)。
|
|
6
|
+
# JS無効時フォールバック: Readable(本文はSSR済み)
|
|
7
|
+
class Tooltip < BaseComponent
|
|
8
|
+
CONTROLLER = "shadcn--tooltip"
|
|
9
|
+
|
|
10
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
11
|
+
def contract_data_attributes
|
|
12
|
+
super.merge(controller: CONTROLLER)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Provider < BaseComponent
|
|
16
|
+
# delayDuration の既定は 0(upstream と同じ)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Trigger < BaseComponent
|
|
20
|
+
include Shadcn::ButtonStyled
|
|
21
|
+
|
|
22
|
+
# 契約タグは TooltipPrimitive.Trigger(HTML名ではない)だが、upstream の
|
|
23
|
+
# トリガーは button 要素として描かれるため button にフォールバックさせる
|
|
24
|
+
sig { override.returns(String) }
|
|
25
|
+
def default_tag
|
|
26
|
+
"button"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
|
30
|
+
def html_attributes
|
|
31
|
+
attributes = apply_button_styling(super)
|
|
32
|
+
attributes[:type] = "button" unless attributes.key?(:type)
|
|
33
|
+
merge_nested(attributes, :data, {
|
|
34
|
+
action: "mouseenter->#{CONTROLLER}#show mouseleave->#{CONTROLLER}#hide focus->#{CONTROLLER}#show blur->#{CONTROLLER}#hide"
|
|
35
|
+
})
|
|
36
|
+
attributes
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Content < BaseComponent
|
|
41
|
+
include Shadcn::FloatingPositionOptions
|
|
42
|
+
|
|
43
|
+
FLOATING_POSITION_DEFAULTS = T.let(
|
|
44
|
+
{ side: :top, align: :center, side_offset: 4, align_offset: 0, collision_padding: 5 }.freeze,
|
|
45
|
+
T::Hash[Symbol, T.untyped]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 契約スロット構成: tooltip-content(arrow は data-slot を持たない装飾要素)
|
|
49
|
+
sig { override.returns(String) }
|
|
50
|
+
def call
|
|
51
|
+
content_tag(:div, **content_attributes) do
|
|
52
|
+
safe_join([content.presence, arrow])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
59
|
+
def content_attributes
|
|
60
|
+
attributes = @html_args.merge(class: self.class.classes(extra: @user_class))
|
|
61
|
+
merge_floating_position_data(attributes, slot: "tooltip-content", state: "closed")
|
|
62
|
+
attributes[:role] = "tooltip"
|
|
63
|
+
attributes[:hidden] = true
|
|
64
|
+
attributes
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# upstream の Arrow(静的クラスは契約スロット外の直書き)
|
|
68
|
+
sig { returns(String) }
|
|
69
|
+
def arrow
|
|
70
|
+
content_tag(:div, class: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground") { "".html_safe }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|