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,182 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rails/generators"
5
+
6
+ module ShadcnViewComponents
7
+ module Generators
8
+ module JavascriptDeliveryMode
9
+ extend T::Sig
10
+
11
+ MODES = %w[importmap bundler].freeze
12
+
13
+ sig { params(value: String).returns(String) }
14
+ def self.validate(value)
15
+ return value if MODES.include?(value)
16
+
17
+ Kernel.raise ArgumentError, "unknown JavaScript delivery mode #{value.inspect} (valid: #{MODES.join(', ')})"
18
+ end
19
+ end
20
+
21
+ # ホストアプリへのインストール手順を自動化する(06-theming-tailwind §5)。
22
+ #
23
+ # - tailwindcss-rails標準のTailwindエントリCSSにEngine CSSの @import 行を追記する
24
+ # - JS登録スニペットの追記を案内する
25
+ # - 冪等であること(二回実行で重複行を作らない)
26
+ class InstallGenerator < Rails::Generators::Base
27
+ extend T::Sig
28
+ include Thor::Actions
29
+
30
+ source_root File.expand_path("templates", __dir__)
31
+
32
+ class_option :javascript, type: :string, default: "importmap",
33
+ desc: "JavaScript delivery mode: importmap or bundler"
34
+
35
+ VENDORED_JAVASCRIPT_PATH = "vendor/shadcn_view_components/javascript"
36
+ TAILWIND_STYLESHEET_PATH = "app/assets/tailwind/application.css"
37
+
38
+ sig { void }
39
+ def append_stylesheets
40
+ JavascriptDeliveryMode.validate(T.cast(options["javascript"], String))
41
+ entry_path = File.expand_path(TAILWIND_STYLESHEET_PATH, destination_root)
42
+ unless File.file?(entry_path)
43
+ Kernel.raise Thor::Error,
44
+ "#{TAILWIND_STYLESHEET_PATH} was not found. Run `bin/rails tailwindcss:install` first."
45
+ end
46
+
47
+ current = File.binread(entry_path)
48
+ normalized = stylesheet_with_directives(current)
49
+
50
+ create_file(TAILWIND_STYLESHEET_PATH, normalized, force: true) unless current == normalized
51
+ end
52
+
53
+ sig { void }
54
+ def install_javascript_package
55
+ return unless bundler_javascript?
56
+
57
+ # このdirectoryはgenerator管理。gem更新時に削除済みfileを残さず完全同期する。
58
+ if behavior == :revoke
59
+ empty_directory(VENDORED_JAVASCRIPT_PATH)
60
+ else
61
+ remove_dir(VENDORED_JAVASCRIPT_PATH)
62
+ directory(javascript_package_path, VENDORED_JAVASCRIPT_PATH, force: true)
63
+ end
64
+ end
65
+
66
+ sig { void }
67
+ def print_js_instructions
68
+ if bundler_javascript?
69
+ say <<~TEXT
70
+
71
+ Interactive components need Stimulus registration. The ESM package was
72
+ synchronized to #{VENDORED_JAVASCRIPT_PATH}. Add the repository-relative
73
+ package with your package manager:
74
+
75
+ pnpm add ./#{VENDORED_JAVASCRIPT_PATH}
76
+ # or: npm install ./#{VENDORED_JAVASCRIPT_PATH}
77
+
78
+ Then add to the bundled JavaScript entry point:
79
+
80
+ import { register } from "@supermomonga/shadcn-view-components"
81
+ register(application)
82
+ TEXT
83
+ else
84
+ say <<~TEXT
85
+
86
+ Interactive components need Stimulus registration. With importmap-rails
87
+ (auto-pinned by the engine), add to app/javascript/application.js:
88
+
89
+ import { register } from "@supermomonga/shadcn-view-components"
90
+ register(application)
91
+ TEXT
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ sig { params(contents: String).returns(String) }
98
+ def stylesheet_with_directives(contents)
99
+ newline = contents.include?("\r\n") ? "\r\n" : "\n"
100
+ lines = contents.lines(chomp: true).reject do |line|
101
+ managed_comment_line?(line) || animate_import_line?(line) || engine_import_line?(line)
102
+ end
103
+ insertion_index = directive_insertion_index(lines)
104
+ while insertion_index.positive? && lines.fetch(insertion_index - 1).empty?
105
+ lines.delete_at(insertion_index - 1)
106
+ insertion_index -= 1
107
+ end
108
+ lines.insert(insertion_index, animate_import, "", managed_comment, engine_import)
109
+
110
+ "#{lines.join(newline)}#{newline}"
111
+ end
112
+
113
+ sig { returns(String) }
114
+ def managed_comment
115
+ "/* shadcn_view_components */"
116
+ end
117
+
118
+ sig { returns(String) }
119
+ def animate_import
120
+ '@import "tw-animate-css";'
121
+ end
122
+
123
+ sig { returns(String) }
124
+ def engine_import
125
+ '@import "../builds/tailwind/shadcn_view_components";'
126
+ end
127
+
128
+ sig { params(line: String).returns(T::Boolean) }
129
+ def managed_comment_line?(line)
130
+ line.strip == managed_comment
131
+ end
132
+
133
+ sig { params(line: String).returns(T::Boolean) }
134
+ def animate_import_line?(line)
135
+ line.match?(/\A[ \t]*@import[ \t]+(["'])tw-animate-css\1[ \t]*;?[ \t]*\z/)
136
+ end
137
+
138
+ sig { params(line: String).returns(T::Boolean) }
139
+ def engine_import_line?(line)
140
+ line.match?(%r{\A[ \t]*@import[ \t]+(["'])\.\./builds/tailwind/shadcn_view_components\1[ \t]*;?[ \t]*\z})
141
+ end
142
+
143
+ sig { params(lines: T::Array[String]).returns(Integer) }
144
+ def directive_insertion_index(lines)
145
+ in_comment = T.let(false, T::Boolean)
146
+
147
+ lines.each_with_index do |line, index|
148
+ stripped = line.strip
149
+ if in_comment
150
+ in_comment = false if stripped.include?("*/")
151
+ elsif stripped.start_with?("/*")
152
+ in_comment = !stripped.include?("*/")
153
+ elsif !stripped.empty? && !prologue_directive?(stripped)
154
+ return index
155
+ end
156
+ end
157
+
158
+ lines.length
159
+ end
160
+
161
+ sig { params(line: String).returns(T::Boolean) }
162
+ def prologue_directive?(line)
163
+ line.match?(/\A@charset[ \t]+/) || line.match?(/\A@import(?:[ \t]+|\()/)
164
+ end
165
+
166
+ sig { returns(String) }
167
+ def gem_root
168
+ Gem.loaded_specs.fetch("shadcn_view_components").full_gem_path
169
+ end
170
+
171
+ sig { returns(String) }
172
+ def javascript_package_path
173
+ File.join(gem_root, "app", "assets", "javascripts", "shadcn")
174
+ end
175
+
176
+ sig { returns(T::Boolean) }
177
+ def bundler_javascript?
178
+ JavascriptDeliveryMode.validate(T.cast(options["javascript"], String)) == "bundler"
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,89 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tailwind_merge"
5
+
6
+ module ShadcnViewComponents
7
+ # 契約(lib/shadcn_view_components/generated/contracts)を引いて、
8
+ # 事前解決済みの最終クラス文字列を返すランタイムヘルパ。
9
+ #
10
+ # Ruby側にマージロジックの再実装を持たないことが upstream との視覚的一致の要点であり、
11
+ # ここで行うのは (1) 組み合わせテーブルの辞書引き (2) 呼び出し元の追加クラスとの統合のみ。
12
+ module Classes
13
+ extend T::Sig
14
+
15
+ # NOTE: module_function はディレクティブ形式で使う(修飾子形式 + rest.kwargsは
16
+ # sorbet-runtimeの実行時検証がrest引数を誤バインドするため)
17
+
18
+ module_function
19
+
20
+ VariantValue = T.type_alias { T.nilable(T.any(Symbol, String)) }
21
+
22
+ MERGER = TailwindMerge::Merger.new
23
+
24
+ sig { params(component: Symbol).returns(T::Module[T.anything]) }
25
+ def contract_for(component)
26
+ constant_path = component.to_s.split("/").map do |segment|
27
+ ShadcnViewComponents::ComponentNaming.constant_name(segment)
28
+ end.join("::")
29
+ ShadcnViewComponents::Contracts.const_get(constant_path, false)
30
+ end
31
+
32
+ sig do
33
+ params(
34
+ component: Symbol,
35
+ extra: T.nilable(String),
36
+ options: T::Hash[Symbol, VariantValue]
37
+ ).returns(String)
38
+ end
39
+ def resolve(component, extra: nil, **options)
40
+ contract = contract_for(component)
41
+ defaults = T.cast(contract.const_get(:DEFAULTS), T::Hash[Symbol, Symbol])
42
+ normalized = {}
43
+ options.each do |prop, value|
44
+ normalized[prop] = normalize_option(component, prop, value) unless value.nil?
45
+ end
46
+ resolved = T.cast(T.unsafe(contract).combination(defaults.merge(normalized)), String)
47
+ # 追加クラスが無いときは事前解決済み文字列をそのまま返す(契約との完全一致を保証)。
48
+ # あるときのみ tailwind_merge で統合する(利用者の上書きが後勝ち)。
49
+ return resolved if extra.nil? || extra.empty?
50
+
51
+ MERGER.merge("#{resolved} #{extra}")
52
+ end
53
+
54
+ # バリアント値の正規化 + fail-fast検証(01-architecture §6.1)。
55
+ # 契約に存在しない値は静かにデフォルトへ落とさず ArgumentError。
56
+ sig { params(component: Symbol, prop: Symbol, value: T.untyped).returns(Symbol) }
57
+ def normalize_option(component, prop, value)
58
+ contract = contract_for(component)
59
+ values = variant_values(contract, prop)
60
+ invalid_variant_value!(contract, prop, value, values) unless value.is_a?(Symbol) || value.is_a?(String)
61
+
62
+ symbol = value.to_sym
63
+ invalid_variant_value!(contract, prop, value, values) unless values.include?(symbol)
64
+ symbol
65
+ end
66
+
67
+ sig { params(contract: T::Module[T.anything], prop: Symbol).returns(T::Array[Symbol]) }
68
+ def variant_values(contract, prop)
69
+ allowed = T.cast(contract.const_get(:VARIANTS), T::Hash[Symbol, T::Array[Symbol]])
70
+ allowed.fetch(prop) do
71
+ Kernel.raise ArgumentError, "unknown variant prop #{prop.inspect} for #{contract} (valid: #{allowed.keys.map(&:inspect).join(', ')})"
72
+ end
73
+ end
74
+
75
+ sig do
76
+ params(
77
+ contract: T::Module[T.anything],
78
+ prop: Symbol,
79
+ value: T.untyped,
80
+ values: T::Array[Symbol]
81
+ ).returns(T.noreturn)
82
+ end
83
+ def invalid_variant_value!(contract, prop, value, values)
84
+ Kernel.raise ArgumentError,
85
+ "unknown variant value #{value.inspect} for #{prop} of #{contract} " \
86
+ "(valid: #{values.map(&:inspect).join(', ')})"
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ShadcnViewComponents
5
+ # file/contract名から、このgemが定義するcomponent定数名を決める。
6
+ # ActiveSupportのglobal inflectionには依存せず、頭字語の規則もここだけで管理する。
7
+ module ComponentNaming
8
+ extend T::Sig
9
+
10
+ CONSTANT_OVERRIDES = T.let({
11
+ "input_otp" => "InputOTP"
12
+ }.freeze, T::Hash[String, String])
13
+
14
+ sig { params(basename: String).returns(String) }
15
+ def self.constant_name(basename)
16
+ CONSTANT_OVERRIDES.fetch(basename) do
17
+ basename.split("_").map(&:capitalize).join
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,95 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # calendar の個別契約(10-roadmap Phase 4 の個別評価ルート)。
5
+ # upstream は react-day-picker の classNames 実行時合成(cn(defaultClassNames.*)
6
+ # や String.raw)を行うため静的抽出の対象外。クラス文字列は tools/extractor と
7
+ # 同一の cva + tailwind-merge で事前解決した値を手動で保守する。
8
+ # upstream 再取得時はこの値の再計算が必要(rake shadcn:generate の対象外である点に注意)
9
+ module ShadcnViewComponents
10
+ module Contracts
11
+ module Calendar
12
+ ROOT_SLOT = T.let("calendar", String)
13
+
14
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
15
+
16
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
17
+
18
+ COMBINATIONS = T.let({
19
+ # 冒頭の flex 群は upstream ラッパー Calendar の className。upstream は
20
+ # md:flex-row で複数月を横並びにできるが、本実装は単月のみの描画のため
21
+ # 入れない(rdp-root は react-day-picker 内部クラスのためDOMには出ない)
22
+ {} => "relative flex flex-col gap-4 group/calendar bg-background p-2 " \
23
+ "[--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] " \
24
+ "in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent w-fit"
25
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
26
+
27
+ SLOTS = T.let([
28
+ {
29
+ name: "calendar",
30
+ tag: "div",
31
+ static_attributes: {}.freeze,
32
+ dynamic_attributes: [].freeze
33
+ }.freeze
34
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
35
+
36
+ extend T::Sig
37
+
38
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
39
+ module_function def combination(options)
40
+ COMBINATIONS.fetch(options)
41
+ end
42
+
43
+ module DayButton
44
+ # upstream は <Button variant="ghost" size="icon"> を土台に day 用クラスを
45
+ # tailwind-merge で合成する(size-8 は size-auto に、px-2.5 は icon サイズの
46
+ # 採用で消える)。px-2.5 等が残るとボタンの最小幅が --cell-size を超え、
47
+ # グリッドが upstream より膨らむ
48
+ ROOT_SLOT = T.let("calendar-day-button", String)
49
+
50
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
51
+
52
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
53
+
54
+ COMBINATIONS = T.let({
55
+ {} => "group/button shrink-0 items-center justify-center rounded-lg border-transparent bg-clip-padding " \
56
+ "text-sm whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring " \
57
+ "focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px " \
58
+ "disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive " \
59
+ "aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 " \
60
+ "dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 " \
61
+ "[&_svg:not([class*='size-'])]:size-4 hover:bg-muted hover:text-foreground " \
62
+ "aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 " \
63
+ "relative isolate z-10 flex " \
64
+ "aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none " \
65
+ "font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 " \
66
+ "group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] " \
67
+ "group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) " \
68
+ "data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary " \
69
+ "data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none " \
70
+ "data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground " \
71
+ "data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) " \
72
+ "data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground " \
73
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground " \
74
+ "dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70"
75
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
76
+
77
+ SLOTS = T.let([
78
+ {
79
+ name: "calendar-day-button",
80
+ tag: "button",
81
+ static_attributes: {}.freeze,
82
+ dynamic_attributes: [].freeze
83
+ }.freeze
84
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
85
+
86
+ extend T::Sig
87
+
88
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
89
+ module_function def combination(options)
90
+ COMBINATIONS.fetch(options)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rails"
5
+
6
+ module ShadcnViewComponents
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace ShadcnViewComponents
9
+
10
+ # Rails engineのcomponentはhost applicationとmain loaderを共有する。
11
+ # fallbackはRails既定のinflectorへ委譲し、このengineのinput_otp.rbだけを
12
+ # InputOTPへ写像することでhostの同名fileとActiveSupportの命名規則を変更しない。
13
+ class ComponentInflector
14
+ extend T::Sig
15
+
16
+ sig { params(delegate: T.untyped, input_otp_path: String).void }
17
+ def initialize(delegate:, input_otp_path:)
18
+ @delegate = T.let(delegate, T.untyped)
19
+ @input_otp_path = T.let(File.expand_path(input_otp_path), String)
20
+ end
21
+
22
+ sig { params(basename: String, abspath: String).returns(String) }
23
+ def camelize(basename, abspath)
24
+ return ShadcnViewComponents::ComponentNaming.constant_name(basename) if basename == "input_otp" && File.expand_path(abspath) == @input_otp_path
25
+
26
+ T.cast(@delegate.camelize(basename, abspath), String)
27
+ end
28
+
29
+ sig { params(overrides: T::Hash[String, String]).void }
30
+ def inflect(overrides)
31
+ @delegate.inflect(overrides)
32
+ end
33
+ end
34
+ private_constant :ComponentInflector
35
+
36
+ initializer "shadcn_view_components.inflector", before: :setup_main_autoloader do
37
+ loader = Rails.autoloaders.main
38
+ input_otp_path = ShadcnViewComponents::Engine.root.join("app/components/shadcn/input_otp.rb").to_s
39
+ loader.inflector = ComponentInflector.new(delegate: loader.inflector, input_otp_path: input_otp_path)
40
+ end
41
+
42
+ initializer "shadcn_view_components.assets" do |app|
43
+ root = ShadcnViewComponents::Engine.root
44
+ app.config.assets.paths << root.join("app/assets/javascripts")
45
+ end
46
+
47
+ # importmap-railsの標準engine統合に従い、map定義とcache監視対象を
48
+ # importmap本体のinitializerより前に追加する。未導入のhostでは何もしない。
49
+ initializer "shadcn_view_components.importmap", before: "importmap" do |app|
50
+ next unless app.config.respond_to?(:importmap)
51
+
52
+ app.config.importmap.paths << root.join("config/importmap.rb")
53
+ app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,112 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # !! AUTO-GENERATED by tools/extractor — DO NOT EDIT !!
5
+ # Source: shadcn/ui base-nova "accordion" (item sha256: dff3a1ce2033aaaa1df6f499f6d9187de7a46572c62d19c71ec5ba375412b5f9)
6
+ # Regenerate with: rake shadcn:generate
7
+
8
+ module ShadcnViewComponents
9
+ module Contracts
10
+ module Accordion
11
+ ROOT_SLOT = T.let("accordion", String)
12
+
13
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
14
+
15
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
16
+
17
+ # キーはソート済みpropペア。値は事前解決済みの最終クラス文字列
18
+ COMBINATIONS = T.let({
19
+ {} => "flex w-full flex-col"
20
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
21
+
22
+ SLOTS = T.let([
23
+ { name: "accordion", tag: "AccordionPrimitive.Root", static_attributes: {}.freeze, dynamic_attributes: [].freeze }.freeze
24
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
25
+
26
+ extend T::Sig
27
+
28
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
29
+ module_function def combination(options)
30
+ COMBINATIONS.fetch(options)
31
+ end
32
+ module Content
33
+ ROOT_SLOT = T.let("accordion-content", String)
34
+
35
+ # 契約クラスが属する要素の data-slot(ルートがラッパー構造の場合)
36
+ CLASSES_SLOT = T.let("", String)
37
+
38
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
39
+
40
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
41
+
42
+ # キーはソート済みpropペア。値は事前解決済みの最終クラス文字列
43
+ COMBINATIONS = T.let({
44
+ {} => "h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4"
45
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
46
+
47
+ SLOTS = T.let([
48
+ { name: "accordion-content", tag: "AccordionPrimitive.Panel", static_attributes: { class: "overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up" }.freeze, dynamic_attributes: [].freeze }.freeze
49
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
50
+
51
+ extend T::Sig
52
+
53
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
54
+ module_function def combination(options)
55
+ COMBINATIONS.fetch(options)
56
+ end
57
+ end
58
+ module Item
59
+ ROOT_SLOT = T.let("accordion-item", String)
60
+
61
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
62
+
63
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
64
+
65
+ # キーはソート済みpropペア。値は事前解決済みの最終クラス文字列
66
+ COMBINATIONS = T.let({
67
+ {} => "not-last:border-b"
68
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
69
+
70
+ SLOTS = T.let([
71
+ { name: "accordion-item", tag: "AccordionPrimitive.Item", static_attributes: {}.freeze, dynamic_attributes: [].freeze }.freeze
72
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
73
+
74
+ extend T::Sig
75
+
76
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
77
+ module_function def combination(options)
78
+ COMBINATIONS.fetch(options)
79
+ end
80
+ end
81
+ module Trigger
82
+ ROOT_SLOT = T.let("", String)
83
+
84
+ # 契約クラスが属する要素の data-slot(ルートがラッパー構造の場合)
85
+ CLASSES_SLOT = T.let("accordion-trigger", String)
86
+
87
+ DEFAULTS = T.let({}.freeze, T::Hash[Symbol, Symbol])
88
+
89
+ VARIANTS = T.let({}.freeze, T::Hash[Symbol, T::Array[Symbol]])
90
+
91
+ # キーはソート済みpropペア。値は事前解決済みの最終クラス文字列
92
+ COMBINATIONS = T.let({
93
+ {} => "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground"
94
+ }.freeze, T::Hash[T::Hash[Symbol, Symbol], String])
95
+
96
+ SLOTS = T.let([
97
+ { name: "", tag: "AccordionPrimitive.Header", static_attributes: { class: "flex" }.freeze, dynamic_attributes: [].freeze }.freeze,
98
+ { name: "accordion-trigger", tag: "AccordionPrimitive.Trigger", static_attributes: {}.freeze, dynamic_attributes: [].freeze }.freeze,
99
+ { name: "accordion-trigger-icon", tag: "IconPlaceholder", static_attributes: { class: "pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden", hugeicons: "ArrowDown01Icon", lucide: "ChevronDownIcon", phosphor: "CaretDownIcon", remixicon: "RiArrowDownSLine", tabler: "IconChevronDown" }.freeze, dynamic_attributes: [].freeze }.freeze,
100
+ { name: "accordion-trigger-icon", tag: "IconPlaceholder", static_attributes: { class: "pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline", hugeicons: "ArrowUp01Icon", lucide: "ChevronUpIcon", phosphor: "CaretUpIcon", remixicon: "RiArrowUpSLine", tabler: "IconChevronUp" }.freeze, dynamic_attributes: [].freeze }.freeze
101
+ ].freeze, T::Array[T::Hash[Symbol, T.untyped]])
102
+
103
+ extend T::Sig
104
+
105
+ sig { params(options: T::Hash[Symbol, Symbol]).returns(String) }
106
+ module_function def combination(options)
107
+ COMBINATIONS.fetch(options)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end