shadcn-phlex 0.1.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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +195 -0
  3. data/app.css +20 -0
  4. data/css/shadcn-source.css +3 -0
  5. data/css/shadcn-tailwind.css +160 -0
  6. data/css/themes/mauve.css +62 -0
  7. data/css/themes/mist.css +62 -0
  8. data/css/themes/neutral.css +74 -0
  9. data/css/themes/olive.css +62 -0
  10. data/css/themes/stone.css +62 -0
  11. data/css/themes/taupe.css +62 -0
  12. data/css/themes/zinc.css +62 -0
  13. data/js/controllers/accordion_controller.js +135 -0
  14. data/js/controllers/checkbox_controller.js +52 -0
  15. data/js/controllers/collapsible_controller.js +85 -0
  16. data/js/controllers/combobox_controller.js +168 -0
  17. data/js/controllers/command_controller.js +171 -0
  18. data/js/controllers/context_menu_controller.js +132 -0
  19. data/js/controllers/dark_mode_controller.js +106 -0
  20. data/js/controllers/dialog_controller.js +205 -0
  21. data/js/controllers/drawer_controller.js +161 -0
  22. data/js/controllers/dropdown_menu_controller.js +189 -0
  23. data/js/controllers/hover_card_controller.js +85 -0
  24. data/js/controllers/index.js +89 -0
  25. data/js/controllers/menubar_controller.js +171 -0
  26. data/js/controllers/navigation_menu_controller.js +160 -0
  27. data/js/controllers/popover_controller.js +151 -0
  28. data/js/controllers/radio_group_controller.js +78 -0
  29. data/js/controllers/scroll_area_controller.js +117 -0
  30. data/js/controllers/select_controller.js +198 -0
  31. data/js/controllers/sheet_controller.js +130 -0
  32. data/js/controllers/slider_controller.js +142 -0
  33. data/js/controllers/switch_controller.js +40 -0
  34. data/js/controllers/tabs_controller.js +96 -0
  35. data/js/controllers/toast_controller.js +206 -0
  36. data/js/controllers/toggle_controller.js +30 -0
  37. data/js/controllers/toggle_group_controller.js +73 -0
  38. data/js/controllers/tooltip_controller.js +146 -0
  39. data/lib/generators/shadcn_phlex/component_generator.rb +79 -0
  40. data/lib/generators/shadcn_phlex/install_generator.rb +217 -0
  41. data/lib/shadcn/base.rb +27 -0
  42. data/lib/shadcn/engine.rb +24 -0
  43. data/lib/shadcn/kit.rb +1158 -0
  44. data/lib/shadcn/themes/accent_colors.rb +106 -0
  45. data/lib/shadcn/themes/base_colors.rb +313 -0
  46. data/lib/shadcn/ui/accordion.rb +135 -0
  47. data/lib/shadcn/ui/alert.rb +79 -0
  48. data/lib/shadcn/ui/alert_dialog.rb +220 -0
  49. data/lib/shadcn/ui/aspect_ratio.rb +35 -0
  50. data/lib/shadcn/ui/avatar.rb +134 -0
  51. data/lib/shadcn/ui/badge.rb +48 -0
  52. data/lib/shadcn/ui/breadcrumb.rb +180 -0
  53. data/lib/shadcn/ui/button.rb +63 -0
  54. data/lib/shadcn/ui/button_group.rb +58 -0
  55. data/lib/shadcn/ui/card.rb +133 -0
  56. data/lib/shadcn/ui/checkbox.rb +72 -0
  57. data/lib/shadcn/ui/collapsible.rb +76 -0
  58. data/lib/shadcn/ui/combobox.rb +229 -0
  59. data/lib/shadcn/ui/command.rb +256 -0
  60. data/lib/shadcn/ui/context_menu.rb +319 -0
  61. data/lib/shadcn/ui/dialog.rb +226 -0
  62. data/lib/shadcn/ui/direction.rb +23 -0
  63. data/lib/shadcn/ui/drawer.rb +217 -0
  64. data/lib/shadcn/ui/dropdown_menu.rb +384 -0
  65. data/lib/shadcn/ui/empty.rb +97 -0
  66. data/lib/shadcn/ui/field.rb +126 -0
  67. data/lib/shadcn/ui/hover_card.rb +75 -0
  68. data/lib/shadcn/ui/input.rb +36 -0
  69. data/lib/shadcn/ui/input_group.rb +32 -0
  70. data/lib/shadcn/ui/input_otp.rb +112 -0
  71. data/lib/shadcn/ui/item.rb +115 -0
  72. data/lib/shadcn/ui/kbd.rb +45 -0
  73. data/lib/shadcn/ui/label.rb +28 -0
  74. data/lib/shadcn/ui/menubar.rb +345 -0
  75. data/lib/shadcn/ui/native_select.rb +31 -0
  76. data/lib/shadcn/ui/navigation_menu.rb +238 -0
  77. data/lib/shadcn/ui/pagination.rb +224 -0
  78. data/lib/shadcn/ui/popover.rb +147 -0
  79. data/lib/shadcn/ui/progress.rb +40 -0
  80. data/lib/shadcn/ui/radio_group.rb +92 -0
  81. data/lib/shadcn/ui/resizable.rb +108 -0
  82. data/lib/shadcn/ui/scroll_area.rb +75 -0
  83. data/lib/shadcn/ui/select.rb +235 -0
  84. data/lib/shadcn/ui/separator.rb +36 -0
  85. data/lib/shadcn/ui/sheet.rb +231 -0
  86. data/lib/shadcn/ui/sidebar.rb +420 -0
  87. data/lib/shadcn/ui/skeleton.rb +23 -0
  88. data/lib/shadcn/ui/slider.rb +72 -0
  89. data/lib/shadcn/ui/sonner.rb +177 -0
  90. data/lib/shadcn/ui/spinner.rb +58 -0
  91. data/lib/shadcn/ui/switch.rb +75 -0
  92. data/lib/shadcn/ui/table.rb +154 -0
  93. data/lib/shadcn/ui/tabs.rb +154 -0
  94. data/lib/shadcn/ui/text_field.rb +146 -0
  95. data/lib/shadcn/ui/textarea.rb +32 -0
  96. data/lib/shadcn/ui/theme_toggle.rb +74 -0
  97. data/lib/shadcn/ui/toggle.rb +66 -0
  98. data/lib/shadcn/ui/toggle_group.rb +75 -0
  99. data/lib/shadcn/ui/tooltip.rb +78 -0
  100. data/lib/shadcn/ui/typography.rb +217 -0
  101. data/lib/shadcn/version.rb +5 -0
  102. data/lib/shadcn-phlex.rb +6 -0
  103. data/lib/shadcn.rb +80 -0
  104. data/package.json +14 -0
  105. data/skills/shadcn-phlex/SKILL.md +190 -0
  106. data/skills/shadcn-phlex/evals/evals.json +90 -0
  107. data/skills/shadcn-phlex/references/component-catalog.md +355 -0
  108. data/skills/shadcn-phlex/rules/composition.md +235 -0
  109. data/skills/shadcn-phlex/rules/forms.md +151 -0
  110. data/skills/shadcn-phlex/rules/helpers.md +54 -0
  111. data/skills/shadcn-phlex/rules/stimulus.md +61 -0
  112. data/skills/shadcn-phlex/rules/styling.md +177 -0
  113. metadata +209 -0
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Compound field component: Field + Label + Input + FieldDescription + FieldError
6
+ #
7
+ # Usage:
8
+ # render Shadcn::UI::TextField.new(
9
+ # label: "Email",
10
+ # name: "user[email]",
11
+ # type: "email",
12
+ # description: "We'll never share your email.",
13
+ # error: "is required",
14
+ # required: true,
15
+ # placeholder: "you@example.com"
16
+ # )
17
+ class TextField < Base
18
+ def initialize(label: nil, name: nil, type: "text", description: nil, error: nil, required: false, disabled: false, id: nil, **input_attrs)
19
+ @label = label
20
+ @name = name
21
+ @type = type
22
+ @description = description
23
+ @error = error
24
+ @required = required
25
+ @disabled = disabled
26
+ @id = id || generate_id(name)
27
+ @input_attrs = input_attrs
28
+ end
29
+
30
+ def view_template
31
+ render Field.new(disabled: @disabled) do
32
+ if @label
33
+ render Label.new(for: @id) do
34
+ plain @label
35
+ if @required
36
+ span(class: "text-destructive") { " *" }
37
+ end
38
+ end
39
+ end
40
+
41
+ render Input.new(
42
+ type: @type,
43
+ name: @name,
44
+ id: @id,
45
+ required: @required || nil,
46
+ disabled: @disabled || nil,
47
+ aria_invalid: @error ? "true" : nil,
48
+ aria_describedby: description_id,
49
+ **@input_attrs
50
+ )
51
+
52
+ if @description && !@error
53
+ render FieldDescription.new(id: description_id) {
54
+ plain @description
55
+ }
56
+ end
57
+
58
+ if @error
59
+ render FieldError.new { plain @error }
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def generate_id(name)
67
+ return nil unless name
68
+ name.to_s.gsub(/[\[\]]/, "_").gsub(/_+/, "_").chomp("_")
69
+ end
70
+
71
+ def description_id
72
+ return nil unless @description && @id
73
+ "#{@id}_description"
74
+ end
75
+ end
76
+
77
+ # Compound field component: Field + Label + Textarea + FieldDescription + FieldError
78
+ #
79
+ # Usage:
80
+ # render Shadcn::UI::TextareaField.new(
81
+ # label: "Message",
82
+ # name: "contact[message]",
83
+ # description: "Max 500 characters.",
84
+ # error: "can't be blank",
85
+ # required: true,
86
+ # rows: 4
87
+ # )
88
+ class TextareaField < Base
89
+ def initialize(label: nil, name: nil, description: nil, error: nil, required: false, disabled: false, id: nil, **textarea_attrs)
90
+ @label = label
91
+ @name = name
92
+ @description = description
93
+ @error = error
94
+ @required = required
95
+ @disabled = disabled
96
+ @id = id || generate_id(name)
97
+ @textarea_attrs = textarea_attrs
98
+ end
99
+
100
+ def view_template
101
+ render Field.new(disabled: @disabled) do
102
+ if @label
103
+ render Label.new(for: @id) do
104
+ plain @label
105
+ if @required
106
+ span(class: "text-destructive") { " *" }
107
+ end
108
+ end
109
+ end
110
+
111
+ render Textarea.new(
112
+ name: @name,
113
+ id: @id,
114
+ required: @required || nil,
115
+ disabled: @disabled || nil,
116
+ aria_invalid: @error ? "true" : nil,
117
+ aria_describedby: description_id,
118
+ **@textarea_attrs
119
+ )
120
+
121
+ if @description && !@error
122
+ render FieldDescription.new(id: description_id) {
123
+ plain @description
124
+ }
125
+ end
126
+
127
+ if @error
128
+ render FieldError.new { plain @error }
129
+ end
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def generate_id(name)
136
+ return nil unless name
137
+ name.to_s.gsub(/[\[\]]/, "_").gsub(/_+/, "_").chomp("_")
138
+ end
139
+
140
+ def description_id
141
+ return nil unless @description && @id
142
+ "#{@id}_description"
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Textarea
6
+ class Textarea < Base
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ textarea(**build_attrs, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def build_attrs
18
+ classes = cn(
19
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2",
20
+ "text-base shadow-xs transition-[color,box-shadow] outline-none",
21
+ "placeholder:text-muted-foreground",
22
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
23
+ "disabled:cursor-not-allowed disabled:opacity-50",
24
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
25
+ "md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
26
+ @attrs.delete(:class)
27
+ )
28
+ @attrs.merge(data_slot: "textarea", class: classes)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Dark mode toggle button
6
+ # Wired to shadcn--dark-mode Stimulus controller
7
+ # Renders a button with sun/moon SVG icons that swap based on dark mode state
8
+ class ThemeToggle < Base
9
+ def initialize(variant: :ghost, size: :icon, **attrs)
10
+ @variant = variant
11
+ @size = size
12
+ @attrs = attrs
13
+ end
14
+
15
+ def view_template(&block)
16
+ div(
17
+ data_controller: "shadcn--dark-mode",
18
+ data_shadcn__dark_mode_mode_value: "system"
19
+ ) do
20
+ render Button.new(
21
+ variant: @variant,
22
+ size: @size,
23
+ data_action: "click->shadcn--dark-mode#toggle",
24
+ **@attrs
25
+ ) do
26
+ if block_given?
27
+ yield
28
+ else
29
+ # Sun icon (visible in dark mode, triggers switch to light)
30
+ svg(
31
+ xmlns: "http://www.w3.org/2000/svg",
32
+ width: "16", height: "16",
33
+ viewbox: "0 0 24 24",
34
+ fill: "none",
35
+ stroke: "currentColor",
36
+ stroke_width: "2",
37
+ stroke_linecap: "round",
38
+ stroke_linejoin: "round",
39
+ class: "size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
40
+ ) do |s|
41
+ s.circle(cx: "12", cy: "12", r: "4")
42
+ s.path(d: "M12 2v2")
43
+ s.path(d: "M12 20v2")
44
+ s.path(d: "m4.93 4.93 1.41 1.41")
45
+ s.path(d: "m17.66 17.66 1.41 1.41")
46
+ s.path(d: "M2 12h2")
47
+ s.path(d: "M20 12h2")
48
+ s.path(d: "m6.34 17.66-1.41 1.41")
49
+ s.path(d: "m19.07 4.93-1.41 1.41")
50
+ end
51
+
52
+ # Moon icon (visible in light mode, triggers switch to dark)
53
+ svg(
54
+ xmlns: "http://www.w3.org/2000/svg",
55
+ width: "16", height: "16",
56
+ viewbox: "0 0 24 24",
57
+ fill: "none",
58
+ stroke: "currentColor",
59
+ stroke_width: "2",
60
+ stroke_linecap: "round",
61
+ stroke_linejoin: "round",
62
+ class: "absolute size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
63
+ ) do |s|
64
+ s.path(d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z")
65
+ end
66
+
67
+ span(class: "sr-only") { "Toggle theme" }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Toggle
6
+ # Variants: default, outline
7
+ # Sizes: default, sm, lg
8
+ class Toggle < Base
9
+ VARIANTS = ClassVariants.build(
10
+ base: [
11
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap",
12
+ "transition-[color,box-shadow] outline-none",
13
+ "hover:bg-muted hover:text-muted-foreground",
14
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
15
+ "disabled:pointer-events-none disabled:opacity-50",
16
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
17
+ "data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
18
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
19
+ ].join(" "),
20
+ variants: {
21
+ variant: {
22
+ default: "bg-transparent",
23
+ outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
24
+ },
25
+ size: {
26
+ default: "h-9 px-2 min-w-9",
27
+ sm: "h-8 px-1.5 min-w-8",
28
+ lg: "h-10 px-2.5 min-w-10"
29
+ }
30
+ },
31
+ defaults: {
32
+ variant: :default,
33
+ size: :default
34
+ }
35
+ )
36
+
37
+ def initialize(variant: :default, size: :default, pressed: false, **attrs)
38
+ @variant = variant
39
+ @size = size
40
+ @pressed = pressed
41
+ @attrs = attrs
42
+ end
43
+
44
+ def view_template(&block)
45
+ button(**build_attrs, &block)
46
+ end
47
+
48
+ private
49
+
50
+ def build_attrs
51
+ classes = cn(VARIANTS.render(variant: @variant, size: @size), @attrs.delete(:class))
52
+ @attrs.merge(
53
+ data_slot: "toggle",
54
+ data_controller: "shadcn--toggle",
55
+ data_shadcn__toggle_target: "button",
56
+ data_action: "click->shadcn--toggle#toggle",
57
+ data_shadcn__toggle_pressed_value: @pressed,
58
+ data_state: @pressed ? "on" : "off",
59
+ type: "button",
60
+ aria_pressed: @pressed,
61
+ class: classes
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui ToggleGroup
6
+ class ToggleGroup < Base
7
+ def initialize(variant: :default, size: :default, spacing: 1, **attrs)
8
+ @variant = variant
9
+ @size = size
10
+ @spacing = spacing
11
+ @attrs = attrs
12
+ end
13
+
14
+ def view_template(&block)
15
+ div(**build_attrs, &block)
16
+ end
17
+
18
+ private
19
+
20
+ def build_attrs
21
+ spacing_classes = if @spacing == 0
22
+ "gap-0 -space-x-px [&_[data-slot=toggle]:first-child]:rounded-l-md [&_[data-slot=toggle]:last-child]:rounded-r-md [&_[data-slot=toggle]:not(:first-child):not(:last-child)]:rounded-none"
23
+ else
24
+ "gap-1"
25
+ end
26
+
27
+ classes = cn(
28
+ "flex items-center",
29
+ spacing_classes,
30
+ @attrs.delete(:class)
31
+ )
32
+ @attrs.merge(
33
+ data_slot: "toggle-group",
34
+ data_controller: "shadcn--toggle-group",
35
+ data_shadcn__toggle_group_type_value: "single",
36
+ data_variant: @variant,
37
+ data_size: @size,
38
+ data_spacing: @spacing,
39
+ role: "group",
40
+ class: classes
41
+ )
42
+ end
43
+ end
44
+
45
+ class ToggleGroupItem < Base
46
+ def initialize(value:, variant: :default, size: :default, pressed: false, **attrs)
47
+ @value = value
48
+ @variant = variant
49
+ @size = size
50
+ @pressed = pressed
51
+ @attrs = attrs
52
+ end
53
+
54
+ def view_template(&block)
55
+ button(**build_attrs, &block)
56
+ end
57
+
58
+ private
59
+
60
+ def build_attrs
61
+ classes = cn(Toggle::VARIANTS.render(variant: @variant, size: @size), @attrs.delete(:class))
62
+ @attrs.merge(
63
+ data_slot: "toggle-group-item",
64
+ data_shadcn__toggle_group_target: "item",
65
+ data_action: "click->shadcn--toggle-group#toggle keydown->shadcn--toggle-group#keydown",
66
+ data_value: @value,
67
+ data_state: @pressed ? "on" : "off",
68
+ type: "button",
69
+ aria_pressed: @pressed,
70
+ class: classes
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Tooltip
6
+ class Tooltip < Base
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(**build_attrs, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def build_attrs
18
+ @attrs.merge(
19
+ data_slot: "tooltip",
20
+ data_controller: "shadcn--tooltip"
21
+ )
22
+ end
23
+ end
24
+
25
+ class TooltipTrigger < Base
26
+ def initialize(**attrs)
27
+ @attrs = attrs
28
+ end
29
+
30
+ def view_template(&block)
31
+ div(**build_attrs, &block)
32
+ end
33
+
34
+ private
35
+
36
+ def build_attrs
37
+ @attrs.merge(
38
+ data_slot: "tooltip-trigger",
39
+ data_shadcn__tooltip_target: "trigger",
40
+ data_action: "mouseenter->shadcn--tooltip#mouseEnter mouseleave->shadcn--tooltip#mouseLeave focusin->shadcn--tooltip#focusIn focusout->shadcn--tooltip#focusOut",
41
+ role: "button",
42
+ style: "display: inline-block"
43
+ )
44
+ end
45
+ end
46
+
47
+ class TooltipContent < Base
48
+ def initialize(**attrs)
49
+ @attrs = attrs
50
+ end
51
+
52
+ def view_template(&block)
53
+ div(**build_attrs, &block)
54
+ end
55
+
56
+ private
57
+
58
+ def build_attrs
59
+ classes = cn(
60
+ "z-50 w-fit rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background",
61
+ "animate-in fade-in-0 zoom-in-95",
62
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
63
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
64
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
65
+ @attrs.delete(:class)
66
+ )
67
+ @attrs.merge(
68
+ data_slot: "tooltip-content",
69
+ data_shadcn__tooltip_target: "content",
70
+ data_action: "mouseenter->shadcn--tooltip#contentMouseEnter mouseleave->shadcn--tooltip#contentMouseLeave",
71
+ role: "tooltip",
72
+ hidden: true,
73
+ class: classes
74
+ )
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Typography components
6
+ # Heading, Paragraph, Blockquote, InlineCode, Lead, Muted, etc.
7
+
8
+ class TypographyH1 < Base
9
+ def initialize(**attrs)
10
+ @attrs = attrs
11
+ end
12
+
13
+ def view_template(&block)
14
+ h1(**build_attrs, &block)
15
+ end
16
+
17
+ private
18
+
19
+ def build_attrs
20
+ classes = cn("scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl", @attrs.delete(:class))
21
+ @attrs.merge(data_slot: "typography-h1", class: classes)
22
+ end
23
+ end
24
+
25
+ class TypographyH2 < Base
26
+ def initialize(**attrs)
27
+ @attrs = attrs
28
+ end
29
+
30
+ def view_template(&block)
31
+ h2(**build_attrs, &block)
32
+ end
33
+
34
+ private
35
+
36
+ def build_attrs
37
+ classes = cn("scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0", @attrs.delete(:class))
38
+ @attrs.merge(data_slot: "typography-h2", class: classes)
39
+ end
40
+ end
41
+
42
+ class TypographyH3 < Base
43
+ def initialize(**attrs)
44
+ @attrs = attrs
45
+ end
46
+
47
+ def view_template(&block)
48
+ h3(**build_attrs, &block)
49
+ end
50
+
51
+ private
52
+
53
+ def build_attrs
54
+ classes = cn("scroll-m-20 text-2xl font-semibold tracking-tight", @attrs.delete(:class))
55
+ @attrs.merge(data_slot: "typography-h3", class: classes)
56
+ end
57
+ end
58
+
59
+ class TypographyH4 < Base
60
+ def initialize(**attrs)
61
+ @attrs = attrs
62
+ end
63
+
64
+ def view_template(&block)
65
+ h4(**build_attrs, &block)
66
+ end
67
+
68
+ private
69
+
70
+ def build_attrs
71
+ classes = cn("scroll-m-20 text-xl font-semibold tracking-tight", @attrs.delete(:class))
72
+ @attrs.merge(data_slot: "typography-h4", class: classes)
73
+ end
74
+ end
75
+
76
+ class TypographyP < Base
77
+ def initialize(**attrs)
78
+ @attrs = attrs
79
+ end
80
+
81
+ def view_template(&block)
82
+ p(**build_attrs, &block)
83
+ end
84
+
85
+ private
86
+
87
+ def build_attrs
88
+ classes = cn("leading-7 [&:not(:first-child)]:mt-6", @attrs.delete(:class))
89
+ @attrs.merge(data_slot: "typography-p", class: classes)
90
+ end
91
+ end
92
+
93
+ class TypographyBlockquote < Base
94
+ def initialize(**attrs)
95
+ @attrs = attrs
96
+ end
97
+
98
+ def view_template(&block)
99
+ blockquote(**build_attrs, &block)
100
+ end
101
+
102
+ private
103
+
104
+ def build_attrs
105
+ classes = cn("mt-6 border-l-2 pl-6 italic", @attrs.delete(:class))
106
+ @attrs.merge(data_slot: "typography-blockquote", class: classes)
107
+ end
108
+ end
109
+
110
+ class TypographyInlineCode < Base
111
+ def initialize(**attrs)
112
+ @attrs = attrs
113
+ end
114
+
115
+ def view_template(&block)
116
+ code(**build_attrs, &block)
117
+ end
118
+
119
+ private
120
+
121
+ def build_attrs
122
+ classes = cn(
123
+ "relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold",
124
+ @attrs.delete(:class)
125
+ )
126
+ @attrs.merge(data_slot: "typography-inline-code", class: classes)
127
+ end
128
+ end
129
+
130
+ class TypographyLead < Base
131
+ def initialize(**attrs)
132
+ @attrs = attrs
133
+ end
134
+
135
+ def view_template(&block)
136
+ p(**build_attrs, &block)
137
+ end
138
+
139
+ private
140
+
141
+ def build_attrs
142
+ classes = cn("text-xl text-muted-foreground", @attrs.delete(:class))
143
+ @attrs.merge(data_slot: "typography-lead", class: classes)
144
+ end
145
+ end
146
+
147
+ class TypographyLarge < Base
148
+ def initialize(**attrs)
149
+ @attrs = attrs
150
+ end
151
+
152
+ def view_template(&block)
153
+ div(**build_attrs, &block)
154
+ end
155
+
156
+ private
157
+
158
+ def build_attrs
159
+ classes = cn("text-lg font-semibold", @attrs.delete(:class))
160
+ @attrs.merge(data_slot: "typography-large", class: classes)
161
+ end
162
+ end
163
+
164
+ class TypographySmall < Base
165
+ def initialize(**attrs)
166
+ @attrs = attrs
167
+ end
168
+
169
+ def view_template(&block)
170
+ small(**build_attrs, &block)
171
+ end
172
+
173
+ private
174
+
175
+ def build_attrs
176
+ classes = cn("text-sm font-medium leading-none", @attrs.delete(:class))
177
+ @attrs.merge(data_slot: "typography-small", class: classes)
178
+ end
179
+ end
180
+
181
+ class TypographyMuted < Base
182
+ def initialize(**attrs)
183
+ @attrs = attrs
184
+ end
185
+
186
+ def view_template(&block)
187
+ p(**build_attrs, &block)
188
+ end
189
+
190
+ private
191
+
192
+ def build_attrs
193
+ classes = cn("text-sm text-muted-foreground", @attrs.delete(:class))
194
+ @attrs.merge(data_slot: "typography-muted", class: classes)
195
+ end
196
+ end
197
+
198
+ class TypographyList < Base
199
+ def initialize(ordered: false, **attrs)
200
+ @ordered = ordered
201
+ @attrs = attrs
202
+ end
203
+
204
+ def view_template(&block)
205
+ tag = @ordered ? :ol : :ul
206
+ send(tag, **build_attrs, &block)
207
+ end
208
+
209
+ private
210
+
211
+ def build_attrs
212
+ classes = cn("my-6 ml-6 list-disc [&>li]:mt-2", @attrs.delete(:class))
213
+ @attrs.merge(data_slot: "typography-list", class: classes)
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Bundler auto-requires based on gem name: shadcn-phlex → shadcn/phlex
4
+ # This shim redirects to the actual entry point at lib/shadcn.rb
5
+ # so users don't need `require: "shadcn"` in their Gemfile.
6
+ require_relative "shadcn"