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,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
|