shadcn_view_components 0.2.1 → 0.2.2
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 +4 -4
- data/README.md +5 -5
- data/app/assets/javascripts/shadcn/controllers/slider_controller.js +14 -9
- data/app/components/shadcn/calendar.rb +6 -3
- data/app/components/shadcn/carousel.rb +5 -5
- data/app/components/shadcn/checkbox.rb +6 -2
- data/app/components/shadcn/radio_group.rb +3 -9
- data/app/components/shadcn/resizable.rb +4 -1
- data/app/components/shadcn/select.rb +7 -1
- data/app/components/shadcn/slider.rb +18 -6
- data/app/components/shadcn/spinner.rb +1 -1
- data/app/components/shadcn/switch.rb +7 -2
- data/lib/shadcn_view_components/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37380475ab9f7c6dd604b818376c3b9ef757d145a5c5d29e832eb5a503f9268a
|
|
4
|
+
data.tar.gz: 142bc9dea6e0ef39f223c3d4d1404de9707e9a7e6ea07b2f872572d3b6000bb9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc6b3e0426da0209f6024f1910bda0d555459e1ea4a7a33bfb29268bdf71b163a3829ce225aa0f71841c336202ef25d8db56a000162c843bfe1e49ffe5d6ca6b
|
|
7
|
+
data.tar.gz: 3e5d0d6d1c29cd72a0001351ec83aceaf7487ba41cd1f652633aaf6a19f07a52d34d71312845a41ae46620ce908961648c03e97c78c635025a5890b9d90e0dd3
|
data/README.md
CHANGED
|
@@ -499,15 +499,15 @@ registry snapshot hash、取得日時、先行検証の結果とworkflow URLを
|
|
|
499
499
|
|
|
500
500
|
```bash
|
|
501
501
|
bundle exec rake parity:run # または mise run parity(ハーネスを明示的に再ビルド)
|
|
502
|
-
PARITY_RATIO=0.01 bundle exec rake parity:run # 閾値を1%に緩和(既定 0.5%)
|
|
503
502
|
bundle exec rake parity:update # 追跡baselineを意図して更新するときだけ実行
|
|
504
503
|
```
|
|
505
504
|
|
|
506
|
-
仕組み: `vendor/shadcn` の tsx を `tools/visual-parity` で展開し、Vite + React で実際に描画(upstream側)。dummy の Lookbook プレビュー(うち側)と同じ Chromium(Cuprite)
|
|
505
|
+
仕組み: `vendor/shadcn` の tsx を `tools/visual-parity` で展開し、Vite + React で実際に描画(upstream側)。dummy の Lookbook プレビュー(うち側)と同じ Chromium(Cuprite)でスクリーンショットを撮り、**画像寸法と復号後の全RGBA値の完全一致**を判定する。upstream側のスタイルは upstream 実アプリの globals.css 相当のみを抽出器が生成した `tools/visual-parity/src/upstream_theme.css`(トークン + npm `shadcn/tailwind.css` の verbatim取り込み)から与えられ、gem の `shadcn.css` とは独立している。これにより shadcn.css への移植漏れ・移植ミス(例: カスタムバリアント未定義でクラスが沈黙する)が upstream 側との差分として検出される(共有してしまうと両側が同じだけ壊れて差分が消えるため)。両側で撮影前にレイアウトとアニメーションを揃え、同一ブラウザ・同一フォントで比較する。各シナリオは **light/dark 両カラースキーム**で撮影する(dark は両側の `<html>` に `.dark` を付与。`dark:bg-destructive/60` 等の dark時ユーティリティや `.dark` トークンの差分はこのモードでしか検出できない)。
|
|
507
506
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
507
|
+
既定の許容差分は0px。画像寸法が異なる場合も失敗する。実装上避けられない差分だけ、
|
|
508
|
+
`spec/coverage/registry.yml` に理由と許容する矩形領域を記録する。現在の例外は、
|
|
509
|
+
ネイティブCSSスクロールバーを使うScrollAreaの右端だけ。領域外のRGBAは常に完全一致を要求する。
|
|
510
|
+
CIの`parity` jobはUbuntu 24.04とChromiumのrevisionを固定し、`system` jobは最新Chromeで動作を検証する。
|
|
511
511
|
|
|
512
512
|
さらにアニメーションパリティ(`spec/visual/animation_parity_spec.rb`)では、両側のコンポーネントを開いた直後に WAAPI でアニメーションを取得・停止し、currentTime を同一チェックポイント(0/25/50/75/100%)に固定した上で補間値(opacity / transform / 高さ)とアニメーション名・持続時間・イージングを比較する。時間を仮想化するため実行タイミングに影響されない(drawer は upstream が vaul のJSバネ物理で動くため対象外)。
|
|
513
513
|
|
|
@@ -18,31 +18,36 @@ export default class SliderController extends Controller {
|
|
|
18
18
|
const input = this.input
|
|
19
19
|
if (!input) return
|
|
20
20
|
|
|
21
|
-
const percentage = this.percentage(input)
|
|
22
21
|
const vertical = this.element.dataset.orientation === "vertical"
|
|
22
|
+
const thumb = this.thumbs[0]
|
|
23
|
+
if (!thumb) return
|
|
24
|
+
|
|
25
|
+
const ratio = this.ratio(input)
|
|
26
|
+
const thumbSize = vertical ? thumb.offsetHeight : thumb.offsetWidth
|
|
27
|
+
const offset = Math.round((0.5 - ratio) * thumbSize * 1000) / 1000
|
|
28
|
+
const position = `calc(${ratio * 100}% + ${offset}px)`
|
|
23
29
|
|
|
24
30
|
for (const range of this.ranges) {
|
|
25
|
-
range.style.width = vertical ? "" :
|
|
26
|
-
range.style.height = vertical ?
|
|
31
|
+
range.style.width = vertical ? "" : position
|
|
32
|
+
range.style.height = vertical ? position : ""
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
for (const thumb of this.thumbs) {
|
|
30
|
-
thumb.style.left = vertical ? "50%" :
|
|
31
|
-
thumb.style.bottom = vertical ?
|
|
36
|
+
thumb.style.left = vertical ? "50%" : position
|
|
37
|
+
thumb.style.bottom = vertical ? position : ""
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
/** @param {HTMLInputElement} input */
|
|
36
|
-
|
|
42
|
+
ratio(input) {
|
|
37
43
|
const min = this.numberOr(input.min, DEFAULT_MIN)
|
|
38
44
|
const max = this.numberOr(input.max, DEFAULT_MAX)
|
|
39
|
-
if (max <= min) return
|
|
45
|
+
if (max <= min) return 0
|
|
40
46
|
|
|
41
47
|
const value = Number.isFinite(input.valueAsNumber)
|
|
42
48
|
? input.valueAsNumber
|
|
43
49
|
: this.numberOr(input.value, min)
|
|
44
|
-
|
|
45
|
-
return `${ratio * 100}%`
|
|
50
|
+
return Math.min(1, Math.max(0, (value - min) / (max - min)))
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/** @param {string} value @param {number} fallback */
|
|
@@ -36,9 +36,12 @@ module Shadcn
|
|
|
36
36
|
sig { override.returns(String) }
|
|
37
37
|
def call
|
|
38
38
|
content_tag(:div, **html_attributes) do
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
# upstreamのmonthsはpadding内の位置基準。ナビのabsolute位置も
|
|
40
|
+
# ルートの外縁ではなくこの内容領域に合わせる。
|
|
41
|
+
content_tag(:div, class: "relative flex flex-col gap-4") do
|
|
42
|
+
navigation = Navigation.new(month: @month, caption_id: caption_id, month_path: @month_path)
|
|
43
|
+
safe_join([render(navigation), month_grid])
|
|
44
|
+
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
@@ -69,7 +69,9 @@ module Shadcn
|
|
|
69
69
|
merge_nested(attributes, :data, { slot: contract_root_slot[:name] })
|
|
70
70
|
content_tag(tag, **attributes) do
|
|
71
71
|
extra = [ORIENTATION_CLASS, @user_class].compact.join(" ")
|
|
72
|
-
|
|
72
|
+
# Emblaは初期位置にもtranslate3d(0,0,0)を付ける。描画レイヤーを
|
|
73
|
+
# 揃えることで、角丸と文字のアンチエイリアスもupstreamと一致する。
|
|
74
|
+
content_tag(:div, class: self.class.classes(extra:), style: "transform: translate3d(0px, 0px, 0px)") { content }
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
|
|
@@ -102,9 +104,7 @@ module Shadcn
|
|
|
102
104
|
ORIENTATION_CLASS = "group-data-vertical/carousel:inset-y-auto " \
|
|
103
105
|
"group-data-vertical/carousel:left-1/2 group-data-vertical/carousel:my-0 " \
|
|
104
106
|
"group-data-vertical/carousel:-translate-x-1/2 group-data-vertical/carousel:rotate-90"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
private_constant :ORIENTATION_CLASS, :RTL_ICON_CLASS
|
|
107
|
+
private_constant :ORIENTATION_CLASS
|
|
108
108
|
|
|
109
109
|
private
|
|
110
110
|
|
|
@@ -169,7 +169,7 @@ module Shadcn
|
|
|
169
169
|
"stroke-linejoin": "round",
|
|
170
170
|
width: "16",
|
|
171
171
|
height: "16",
|
|
172
|
-
class: "size-4
|
|
172
|
+
class: "size-4",
|
|
173
173
|
aria: { hidden: "true" }
|
|
174
174
|
) do
|
|
175
175
|
raw(paths)
|
|
@@ -8,9 +8,13 @@ module Shadcn
|
|
|
8
8
|
NATIVE_STATE_CLASS = T.let([
|
|
9
9
|
"not-checked:border-input not-checked:bg-transparent dark:not-checked:bg-input/30",
|
|
10
10
|
"checked:border-primary checked:bg-primary checked:text-primary-foreground",
|
|
11
|
-
"group-has-[:focus-visible]/field-label:checked:border-primary dark:checked:bg-primary"
|
|
11
|
+
"group-has-[:focus-visible]/field-label:checked:border-primary dark:checked:bg-primary",
|
|
12
|
+
"disabled:opacity-100"
|
|
12
13
|
].join(" ").freeze, String)
|
|
13
14
|
|
|
15
|
+
# input の選択時の文字色は兄弟の indicator に継承されない。
|
|
16
|
+
INDICATOR_STATE_CLASS = "pointer-events-none absolute inset-0 hidden peer-checked:grid peer-checked:text-primary-foreground"
|
|
17
|
+
|
|
14
18
|
# 契約タグは CheckboxPrimitive.Root。ネイティブな input[type=checkbox] として描く
|
|
15
19
|
# (JS無しで動作 — 05-stimulus-hotwire §3「checkbox = input + CSS」)。
|
|
16
20
|
# base-nova の契約クラスは data-checked / data-unchecked(属性の存在)を参照する。
|
|
@@ -44,7 +48,7 @@ module Shadcn
|
|
|
44
48
|
sig { returns(String) }
|
|
45
49
|
def indicator_element
|
|
46
50
|
contract_class = T.cast(contract_slot("checkbox-indicator").dig(:static_attributes, :class), T.nilable(String))
|
|
47
|
-
content_tag(:span, data: { slot: "checkbox-indicator" }, aria: { hidden: "true" }, class: [contract_class,
|
|
51
|
+
content_tag(:span, data: { slot: "checkbox-indicator" }, aria: { hidden: "true" }, class: [contract_class, INDICATOR_STATE_CLASS].compact.join(" ")) do
|
|
48
52
|
content_tag(
|
|
49
53
|
:svg,
|
|
50
54
|
xmlns: "http://www.w3.org/2000/svg",
|
|
@@ -58,15 +58,9 @@ module Shadcn
|
|
|
58
58
|
class: [indicator_class, "pointer-events-none absolute inset-0 hidden peer-checked:flex"].compact.join(" ")
|
|
59
59
|
) do
|
|
60
60
|
content_tag(
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
fill: "none",
|
|
65
|
-
stroke: "none",
|
|
66
|
-
class: "absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current"
|
|
67
|
-
) do
|
|
68
|
-
raw(%(<circle cx="12" cy="12" r="10"/>))
|
|
69
|
-
end
|
|
61
|
+
:span,
|
|
62
|
+
class: "absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground"
|
|
63
|
+
) { "".html_safe }
|
|
70
64
|
end
|
|
71
65
|
end
|
|
72
66
|
end
|
|
@@ -25,7 +25,10 @@ module Shadcn
|
|
|
25
25
|
def html_attributes
|
|
26
26
|
attributes = super
|
|
27
27
|
attributes[:role] = "group"
|
|
28
|
-
|
|
28
|
+
# groupロールにaria-orientationは許可されない。縦配置はCSSで指定する。
|
|
29
|
+
style = { display: "flex" }
|
|
30
|
+
style[:flex_direction] = "column" if @orientation == "vertical"
|
|
31
|
+
merge_style(attributes, style)
|
|
29
32
|
merge_nested(attributes, :data, { controller: CONTROLLER, orientation: @orientation })
|
|
30
33
|
attributes
|
|
31
34
|
end
|
|
@@ -158,7 +158,10 @@ module Shadcn
|
|
|
158
158
|
attributes = @html_args.merge(class: self.class.classes(extra: @user_class))
|
|
159
159
|
attributes[:role] = "presentation" unless attributes.key?(:role)
|
|
160
160
|
attributes[:popover] = "auto" unless attributes.key?(:popover)
|
|
161
|
-
|
|
161
|
+
# upstreamのPositionerはtransformを持つ描画レイヤー。ネイティブpopoverは
|
|
162
|
+
# top layerへ移るため、自身にも同じレイヤーを作り色の合成を揃える。
|
|
163
|
+
merge_style(attributes, display: "flex", flex_direction: "column", overflow: "hidden",
|
|
164
|
+
transform: "translate3d(0px, 0px, 0px)")
|
|
162
165
|
merge_floating_position_data(
|
|
163
166
|
attributes,
|
|
164
167
|
slot: "select-content",
|
|
@@ -202,6 +205,9 @@ module Shadcn
|
|
|
202
205
|
attributes = super
|
|
203
206
|
attributes[:role] = "option"
|
|
204
207
|
attributes[:tabindex] = "-1"
|
|
208
|
+
# upstreamは現在項目をfocusしてfocus:*を適用する。こちらはトリガーに
|
|
209
|
+
# focusを保持するため、同じ強調をdata-highlightedで反映する。
|
|
210
|
+
attributes[:class] = [attributes[:class], "data-[highlighted=true]:bg-accent data-[highlighted=true]:text-accent-foreground"].compact.join(" ")
|
|
205
211
|
merge_nested(attributes, :aria, { selected: "false" }.merge(@disabled ? { disabled: "true" } : {}))
|
|
206
212
|
item_data = {
|
|
207
213
|
value: @value,
|
|
@@ -46,18 +46,18 @@ module Shadcn
|
|
|
46
46
|
sig { returns(String) }
|
|
47
47
|
def range_style
|
|
48
48
|
if vertical?
|
|
49
|
-
"position: absolute; bottom: 0; height: #{
|
|
49
|
+
"position: absolute; bottom: 0; height: #{edge_aligned_position}"
|
|
50
50
|
else
|
|
51
|
-
"position: absolute; left: 0; width: #{
|
|
51
|
+
"position: absolute; left: 0; width: #{edge_aligned_position}"
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
sig { returns(String) }
|
|
56
56
|
def thumb_style
|
|
57
57
|
if vertical?
|
|
58
|
-
"position: absolute; bottom: #{
|
|
58
|
+
"position: absolute; bottom: #{edge_aligned_position}; left: 50%; transform: translate(-50%, 50%)"
|
|
59
59
|
else
|
|
60
|
-
"position: absolute; left: #{
|
|
60
|
+
"position: absolute; left: #{edge_aligned_position}; top: 50%; transform: translate(-50%, -50%)"
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -90,8 +90,20 @@ module Shadcn
|
|
|
90
90
|
|
|
91
91
|
sig { returns(String) }
|
|
92
92
|
def percentage
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
format("%.12g", (fraction * 100).to_f)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# upstreamのthumbAlignment="edge"は12pxのつまみ中心を、両端6pxを
|
|
97
|
+
# 除いた範囲内で動かす。rangeの終端も同じ位置に合わせる。
|
|
98
|
+
sig { returns(String) }
|
|
99
|
+
def edge_aligned_position
|
|
100
|
+
offset = 6 - (12 * fraction)
|
|
101
|
+
"calc(#{percentage}% + #{format('%.12g', offset.to_f)}px)"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
sig { returns(Rational) }
|
|
105
|
+
def fraction
|
|
106
|
+
(effective_value - decimal(@min)) / (decimal(@max) - decimal(@min))
|
|
95
107
|
end
|
|
96
108
|
|
|
97
109
|
sig { returns(Rational) }
|
|
@@ -37,7 +37,10 @@ module Shadcn
|
|
|
37
37
|
|
|
38
38
|
sig { override.returns(String) }
|
|
39
39
|
def call
|
|
40
|
-
|
|
40
|
+
# 装飾をflex内の通常要素にして、upstreamと同じ行内ボックスの基準位置を使う。
|
|
41
|
+
# inputは外枠に重ね、ネイティブのチェック状態とフォーム値を保持する。
|
|
42
|
+
wrapper_size = @size == "sm" ? "h-[14px] w-[24px]" : "h-[18.4px] w-[32px]"
|
|
43
|
+
content_tag(:span, class: "relative inline-flex shrink-0 items-center rounded-full border border-transparent #{wrapper_size}") do
|
|
41
44
|
safe_join([input_element, thumb_element])
|
|
42
45
|
end
|
|
43
46
|
end
|
|
@@ -51,6 +54,8 @@ module Shadcn
|
|
|
51
54
|
# 背景/角丸の上にOS標準の箱を塗る(dark時は白箱として視覚差になる)ため消す。
|
|
52
55
|
# 契約の data-[state] 系背景クラスを素の要素に効かせるために必要
|
|
53
56
|
attributes[:class] = [attributes[:class], "appearance-none", NATIVE_STATE_CLASS].compact.join(" ")
|
|
57
|
+
# 外枠の透明な1px borderを覆い、上流のrootと同じ描画位置に置く。
|
|
58
|
+
merge_style(attributes, position: "absolute", top: "-1px", left: "-1px")
|
|
54
59
|
merge_native_checked_state(attributes, checked: native_checked?(@html_args))
|
|
55
60
|
void_tag("input", **attributes)
|
|
56
61
|
end
|
|
@@ -63,7 +68,7 @@ module Shadcn
|
|
|
63
68
|
# 発火しない。sizeはコンポーネント側で明示する(契約のdata-size値と同じ)
|
|
64
69
|
size_class = @size == "sm" ? "size-3" : "size-4"
|
|
65
70
|
decoration = [
|
|
66
|
-
"pointer-events-none
|
|
71
|
+
"pointer-events-none relative",
|
|
67
72
|
"peer-checked:translate-x-[calc(100%-2px)] peer-not-checked:translate-x-0",
|
|
68
73
|
"dark:peer-checked:bg-primary-foreground dark:peer-not-checked:bg-foreground transition-transform",
|
|
69
74
|
size_class
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shadcn_view_components
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- supermomonga
|
|
@@ -441,7 +441,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
441
441
|
- !ruby/object:Gem::Version
|
|
442
442
|
version: '0'
|
|
443
443
|
requirements: []
|
|
444
|
-
rubygems_version: 4.0.
|
|
444
|
+
rubygems_version: 4.0.20
|
|
445
445
|
specification_version: 4
|
|
446
446
|
summary: shadcn/ui components as Rails ViewComponents with a deterministic sync pipeline
|
|
447
447
|
test_files: []
|