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.
Files changed (177) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +590 -0
  3. data/LICENSE +49 -0
  4. data/README.md +542 -0
  5. data/app/assets/javascripts/shadcn/aria_relationships.js +88 -0
  6. data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +241 -0
  7. data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +98 -0
  8. data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +305 -0
  9. data/app/assets/javascripts/shadcn/controllers/checked_state_controller.js +121 -0
  10. data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +613 -0
  11. data/app/assets/javascripts/shadcn/controllers/command_controller.js +87 -0
  12. data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +208 -0
  13. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +145 -0
  14. data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +416 -0
  15. data/app/assets/javascripts/shadcn/controllers/menu_controller.js +286 -0
  16. data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +430 -0
  17. data/app/assets/javascripts/shadcn/controllers/message_scroller_controller.js +37 -0
  18. data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +254 -0
  19. data/app/assets/javascripts/shadcn/controllers/popover_controller.js +117 -0
  20. data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +256 -0
  21. data/app/assets/javascripts/shadcn/controllers/select_controller.js +491 -0
  22. data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +30 -0
  23. data/app/assets/javascripts/shadcn/controllers/slider_controller.js +74 -0
  24. data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +146 -0
  25. data/app/assets/javascripts/shadcn/controllers/toast_controller.js +81 -0
  26. data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +21 -0
  27. data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +37 -0
  28. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +123 -0
  29. data/app/assets/javascripts/shadcn/floating_position.js +660 -0
  30. data/app/assets/javascripts/shadcn/hide_after_exit.js +53 -0
  31. data/app/assets/javascripts/shadcn/index.js +65 -0
  32. data/app/assets/javascripts/shadcn/menu_popover.js +352 -0
  33. data/app/assets/javascripts/shadcn/package.json +17 -0
  34. data/app/assets/javascripts/shadcn/state_attrs.js +13 -0
  35. data/app/assets/stylesheets/shadcn/shadcn.css +766 -0
  36. data/app/assets/tailwind/shadcn_view_components/engine.css +11 -0
  37. data/app/components/shadcn/accordion.rb +121 -0
  38. data/app/components/shadcn/alert.rb +31 -0
  39. data/app/components/shadcn/alert_dialog.rb +165 -0
  40. data/app/components/shadcn/aspect_ratio.rb +34 -0
  41. data/app/components/shadcn/attachment.rb +86 -0
  42. data/app/components/shadcn/avatar.rb +55 -0
  43. data/app/components/shadcn/badge.rb +32 -0
  44. data/app/components/shadcn/base_component.rb +239 -0
  45. data/app/components/shadcn/breadcrumb.rb +70 -0
  46. data/app/components/shadcn/bubble.rb +48 -0
  47. data/app/components/shadcn/button.html.erb +1 -0
  48. data/app/components/shadcn/button.rb +41 -0
  49. data/app/components/shadcn/button_group.rb +41 -0
  50. data/app/components/shadcn/button_styled.rb +51 -0
  51. data/app/components/shadcn/calendar.rb +276 -0
  52. data/app/components/shadcn/card.rb +18 -0
  53. data/app/components/shadcn/carousel.rb +236 -0
  54. data/app/components/shadcn/chart.rb +57 -0
  55. data/app/components/shadcn/checkbox.rb +64 -0
  56. data/app/components/shadcn/collapsible.rb +25 -0
  57. data/app/components/shadcn/combobox.rb +552 -0
  58. data/app/components/shadcn/command.rb +149 -0
  59. data/app/components/shadcn/context_menu.rb +79 -0
  60. data/app/components/shadcn/dialog.rb +180 -0
  61. data/app/components/shadcn/direction_provider.rb +18 -0
  62. data/app/components/shadcn/drawer.rb +150 -0
  63. data/app/components/shadcn/dropdown_menu.rb +242 -0
  64. data/app/components/shadcn/empty.rb +40 -0
  65. data/app/components/shadcn/field.rb +106 -0
  66. data/app/components/shadcn/floating_position_options.rb +132 -0
  67. data/app/components/shadcn/form.rb +105 -0
  68. data/app/components/shadcn/hover_card.rb +66 -0
  69. data/app/components/shadcn/input.rb +13 -0
  70. data/app/components/shadcn/input_group/button.rb +48 -0
  71. data/app/components/shadcn/input_group.rb +51 -0
  72. data/app/components/shadcn/input_otp.rb +265 -0
  73. data/app/components/shadcn/item.rb +108 -0
  74. data/app/components/shadcn/kbd.rb +9 -0
  75. data/app/components/shadcn/label.rb +12 -0
  76. data/app/components/shadcn/marker.rb +37 -0
  77. data/app/components/shadcn/menubar.rb +110 -0
  78. data/app/components/shadcn/message.rb +29 -0
  79. data/app/components/shadcn/message_scroller.rb +52 -0
  80. data/app/components/shadcn/native_checked_state.rb +74 -0
  81. data/app/components/shadcn/native_select.rb +78 -0
  82. data/app/components/shadcn/navigation_menu.rb +122 -0
  83. data/app/components/shadcn/pagination.rb +160 -0
  84. data/app/components/shadcn/popover.rb +68 -0
  85. data/app/components/shadcn/preview_base.rb +107 -0
  86. data/app/components/shadcn/progress.rb +49 -0
  87. data/app/components/shadcn/radio_group.rb +74 -0
  88. data/app/components/shadcn/resizable.rb +77 -0
  89. data/app/components/shadcn/scroll_area.rb +59 -0
  90. data/app/components/shadcn/select.rb +356 -0
  91. data/app/components/shadcn/separator.rb +42 -0
  92. data/app/components/shadcn/sheet.rb +157 -0
  93. data/app/components/shadcn/sidebar.rb +332 -0
  94. data/app/components/shadcn/skeleton.rb +13 -0
  95. data/app/components/shadcn/slider.rb +279 -0
  96. data/app/components/shadcn/sonner.rb +30 -0
  97. data/app/components/shadcn/spinner.rb +32 -0
  98. data/app/components/shadcn/switch.rb +79 -0
  99. data/app/components/shadcn/table.rb +46 -0
  100. data/app/components/shadcn/tabs.rb +119 -0
  101. data/app/components/shadcn/textarea.rb +7 -0
  102. data/app/components/shadcn/toggle.rb +66 -0
  103. data/app/components/shadcn/toggle_group.rb +127 -0
  104. data/app/components/shadcn/tooltip.rb +74 -0
  105. data/config/importmap.rb +5 -0
  106. data/lib/generators/shadcn_view_components/install_generator.rb +182 -0
  107. data/lib/shadcn_view_components/classes.rb +89 -0
  108. data/lib/shadcn_view_components/component_naming.rb +21 -0
  109. data/lib/shadcn_view_components/contracts/calendar.rb +95 -0
  110. data/lib/shadcn_view_components/engine.rb +56 -0
  111. data/lib/shadcn_view_components/generated/contracts/accordion.rb +112 -0
  112. data/lib/shadcn_view_components/generated/contracts/alert-dialog.rb +300 -0
  113. data/lib/shadcn_view_components/generated/contracts/alert.rb +104 -0
  114. data/lib/shadcn_view_components/generated/contracts/aspect-ratio.rb +34 -0
  115. data/lib/shadcn_view_components/generated/contracts/attachment.rb +224 -0
  116. data/lib/shadcn_view_components/generated/contracts/avatar.rb +149 -0
  117. data/lib/shadcn_view_components/generated/contracts/badge.rb +39 -0
  118. data/lib/shadcn_view_components/generated/contracts/breadcrumb.rb +172 -0
  119. data/lib/shadcn_view_components/generated/contracts/bubble.rb +112 -0
  120. data/lib/shadcn_view_components/generated/contracts/button-group.rb +81 -0
  121. data/lib/shadcn_view_components/generated/contracts/button.rb +81 -0
  122. data/lib/shadcn_view_components/generated/contracts/card.rb +172 -0
  123. data/lib/shadcn_view_components/generated/contracts/carousel.rb +133 -0
  124. data/lib/shadcn_view_components/generated/contracts/chart.rb +109 -0
  125. data/lib/shadcn_view_components/generated/contracts/checkbox.rb +35 -0
  126. data/lib/shadcn_view_components/generated/contracts/collapsible.rb +89 -0
  127. data/lib/shadcn_view_components/generated/contracts/combobox.rb +368 -0
  128. data/lib/shadcn_view_components/generated/contracts/command.rb +222 -0
  129. data/lib/shadcn_view_components/generated/contracts/context-menu.rb +378 -0
  130. data/lib/shadcn_view_components/generated/contracts/dialog.rb +258 -0
  131. data/lib/shadcn_view_components/generated/contracts/direction.rb +34 -0
  132. data/lib/shadcn_view_components/generated/contracts/drawer.rb +280 -0
  133. data/lib/shadcn_view_components/generated/contracts/dropdown-menu.rb +380 -0
  134. data/lib/shadcn_view_components/generated/contracts/empty.rb +150 -0
  135. data/lib/shadcn_view_components/generated/contracts/field.rb +244 -0
  136. data/lib/shadcn_view_components/generated/contracts/form.rb +62 -0
  137. data/lib/shadcn_view_components/generated/contracts/hover-card.rb +90 -0
  138. data/lib/shadcn_view_components/generated/contracts/input-group.rb +155 -0
  139. data/lib/shadcn_view_components/generated/contracts/input-otp.rb +106 -0
  140. data/lib/shadcn_view_components/generated/contracts/input.rb +34 -0
  141. data/lib/shadcn_view_components/generated/contracts/item.rb +251 -0
  142. data/lib/shadcn_view_components/generated/contracts/kbd.rb +57 -0
  143. data/lib/shadcn_view_components/generated/contracts/label.rb +34 -0
  144. data/lib/shadcn_view_components/generated/contracts/marker.rb +82 -0
  145. data/lib/shadcn_view_components/generated/contracts/menubar.rb +394 -0
  146. data/lib/shadcn_view_components/generated/contracts/message-scroller.rb +149 -0
  147. data/lib/shadcn_view_components/generated/contracts/message.rb +149 -0
  148. data/lib/shadcn_view_components/generated/contracts/native-select.rb +82 -0
  149. data/lib/shadcn_view_components/generated/contracts/navigation-menu.rb +195 -0
  150. data/lib/shadcn_view_components/generated/contracts/pagination.rb +176 -0
  151. data/lib/shadcn_view_components/generated/contracts/popover.rb +159 -0
  152. data/lib/shadcn_view_components/generated/contracts/progress.rb +126 -0
  153. data/lib/shadcn_view_components/generated/contracts/radio-group.rb +58 -0
  154. data/lib/shadcn_view_components/generated/contracts/resizable.rb +85 -0
  155. data/lib/shadcn_view_components/generated/contracts/scroll-area.rb +59 -0
  156. data/lib/shadcn_view_components/generated/contracts/select.rb +245 -0
  157. data/lib/shadcn_view_components/generated/contracts/separator.rb +34 -0
  158. data/lib/shadcn_view_components/generated/contracts/sheet.rb +209 -0
  159. data/lib/shadcn_view_components/generated/contracts/sidebar.rb +558 -0
  160. data/lib/shadcn_view_components/generated/contracts/skeleton.rb +34 -0
  161. data/lib/shadcn_view_components/generated/contracts/slider.rb +38 -0
  162. data/lib/shadcn_view_components/generated/contracts/sonner.rb +36 -0
  163. data/lib/shadcn_view_components/generated/contracts/spinner.rb +34 -0
  164. data/lib/shadcn_view_components/generated/contracts/switch.rb +35 -0
  165. data/lib/shadcn_view_components/generated/contracts/table.rb +199 -0
  166. data/lib/shadcn_view_components/generated/contracts/tabs.rb +104 -0
  167. data/lib/shadcn_view_components/generated/contracts/textarea.rb +34 -0
  168. data/lib/shadcn_view_components/generated/contracts/toggle-group.rb +62 -0
  169. data/lib/shadcn_view_components/generated/contracts/toggle.rb +39 -0
  170. data/lib/shadcn_view_components/generated/contracts/tooltip.rb +116 -0
  171. data/lib/shadcn_view_components/numeric_property_validation.rb +124 -0
  172. data/lib/shadcn_view_components/property_contract_definitions.rb +92 -0
  173. data/lib/shadcn_view_components/property_contracts.rb +42 -0
  174. data/lib/shadcn_view_components/property_validation.rb +55 -0
  175. data/lib/shadcn_view_components/version.rb +6 -0
  176. data/lib/shadcn_view_components.rb +21 -0
  177. 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,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Shadcn
5
+ class Textarea < BaseComponent
6
+ end
7
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ pin_all_from File.expand_path("../app/assets/javascripts/shadcn", __dir__),
4
+ under: "@supermomonga/shadcn-view-components",
5
+ to: "shadcn"