view_primitives 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +198 -0
- data/lib/generators/view_primitives/add/add_generator.rb +110 -0
- data/lib/generators/view_primitives/add/templates/accordion/accordion_component.html.erb +10 -0
- data/lib/generators/view_primitives/add/templates/accordion/accordion_component.rb.tt +22 -0
- data/lib/generators/view_primitives/add/templates/accordion/accordion_controller.js +15 -0
- data/lib/generators/view_primitives/add/templates/accordion/accordion_item_component.rb.tt +47 -0
- data/lib/generators/view_primitives/add/templates/alert/alert_component.rb.tt +62 -0
- data/lib/generators/view_primitives/add/templates/alert_dialog/alert_dialog_component.rb.tt +55 -0
- data/lib/generators/view_primitives/add/templates/aspect_ratio/aspect_ratio_component.rb.tt +18 -0
- data/lib/generators/view_primitives/add/templates/audio/audio_component.rb.tt +51 -0
- data/lib/generators/view_primitives/add/templates/avatar/avatar_component.rb.tt +37 -0
- data/lib/generators/view_primitives/add/templates/badge/badge_component.rb.tt +35 -0
- data/lib/generators/view_primitives/add/templates/banner/banner_component.rb.tt +29 -0
- data/lib/generators/view_primitives/add/templates/bottom_nav/bottom_nav_component.rb.tt +38 -0
- data/lib/generators/view_primitives/add/templates/breadcrumb/breadcrumb_component.rb.tt +37 -0
- data/lib/generators/view_primitives/add/templates/button/button_component.rb.tt +61 -0
- data/lib/generators/view_primitives/add/templates/button_group/button_group_component.rb.tt +23 -0
- data/lib/generators/view_primitives/add/templates/calendar/calendar_component.rb.tt +121 -0
- data/lib/generators/view_primitives/add/templates/calendar/calendar_controller.js +86 -0
- data/lib/generators/view_primitives/add/templates/card/card_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/card/card_content_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/card/card_description_component.rb.tt +17 -0
- data/lib/generators/view_primitives/add/templates/card/card_footer_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/card/card_header_component.rb.tt +17 -0
- data/lib/generators/view_primitives/add/templates/card/card_title_component.rb.tt +17 -0
- data/lib/generators/view_primitives/add/templates/carousel/carousel_component.rb.tt +102 -0
- data/lib/generators/view_primitives/add/templates/carousel/carousel_controller.js +48 -0
- data/lib/generators/view_primitives/add/templates/chart/chart_component.rb.tt +63 -0
- data/lib/generators/view_primitives/add/templates/chart/chart_controller.js +29 -0
- data/lib/generators/view_primitives/add/templates/chat_bubble/chat_bubble_component.rb.tt +53 -0
- data/lib/generators/view_primitives/add/templates/checkbox/checkbox_component.rb.tt +50 -0
- data/lib/generators/view_primitives/add/templates/collapsible/collapsible_component.rb.tt +31 -0
- data/lib/generators/view_primitives/add/templates/combobox/combobox_component.rb.tt +87 -0
- data/lib/generators/view_primitives/add/templates/combobox/combobox_controller.js +38 -0
- data/lib/generators/view_primitives/add/templates/command/command_component.rb.tt +85 -0
- data/lib/generators/view_primitives/add/templates/command/command_controller.js +50 -0
- data/lib/generators/view_primitives/add/templates/context_menu/context_menu_component.rb.tt +47 -0
- data/lib/generators/view_primitives/add/templates/context_menu/context_menu_controller.js +20 -0
- data/lib/generators/view_primitives/add/templates/data_table/data_table_component.rb.tt +163 -0
- data/lib/generators/view_primitives/add/templates/data_table/data_table_controller.js +115 -0
- data/lib/generators/view_primitives/add/templates/date_picker/date_picker_component.rb.tt +92 -0
- data/lib/generators/view_primitives/add/templates/date_picker/date_picker_controller.js +48 -0
- data/lib/generators/view_primitives/add/templates/device_mockup/device_mockup_component.rb.tt +65 -0
- data/lib/generators/view_primitives/add/templates/dialog/dialog_component.rb.tt +71 -0
- data/lib/generators/view_primitives/add/templates/dialog/dialog_controller.js +15 -0
- data/lib/generators/view_primitives/add/templates/drawer/drawer_component.rb.tt +62 -0
- data/lib/generators/view_primitives/add/templates/drawer/drawer_controller.js +15 -0
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_controller.js +17 -0
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt +53 -0
- data/lib/generators/view_primitives/add/templates/embed/embed_component.rb.tt +271 -0
- data/lib/generators/view_primitives/add/templates/embed/embed_controller.js +43 -0
- data/lib/generators/view_primitives/add/templates/figure/figure_component.rb.tt +24 -0
- data/lib/generators/view_primitives/add/templates/file_input/file_input_component.rb.tt +31 -0
- data/lib/generators/view_primitives/add/templates/floating_label/floating_label_component.rb.tt +54 -0
- data/lib/generators/view_primitives/add/templates/footer/footer_component.rb.tt +51 -0
- data/lib/generators/view_primitives/add/templates/form_field/form_field_component.rb.tt +51 -0
- data/lib/generators/view_primitives/add/templates/gallery/gallery_component.rb.tt +83 -0
- data/lib/generators/view_primitives/add/templates/gallery/gallery_controller.js +28 -0
- data/lib/generators/view_primitives/add/templates/hover_card/hover_card_component.rb.tt +43 -0
- data/lib/generators/view_primitives/add/templates/iframe/iframe_component.rb.tt +59 -0
- data/lib/generators/view_primitives/add/templates/image/image_component.rb.tt +38 -0
- data/lib/generators/view_primitives/add/templates/indicator/indicator_component.rb.tt +46 -0
- data/lib/generators/view_primitives/add/templates/input/input_component.rb.tt +28 -0
- data/lib/generators/view_primitives/add/templates/input_otp/input_otp_component.rb.tt +65 -0
- data/lib/generators/view_primitives/add/templates/input_otp/input_otp_controller.js +39 -0
- data/lib/generators/view_primitives/add/templates/kbd/kbd_component.rb.tt +21 -0
- data/lib/generators/view_primitives/add/templates/label/label_component.rb.tt +23 -0
- data/lib/generators/view_primitives/add/templates/list_group/list_group_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/list_group/list_group_item_component.rb.tt +31 -0
- data/lib/generators/view_primitives/add/templates/map_area/map_area_component.rb.tt +72 -0
- data/lib/generators/view_primitives/add/templates/mega_menu/mega_menu_component.rb.tt +130 -0
- data/lib/generators/view_primitives/add/templates/mega_menu/mega_menu_controller.js +23 -0
- data/lib/generators/view_primitives/add/templates/menubar/menubar_component.rb.tt +33 -0
- data/lib/generators/view_primitives/add/templates/menubar/menubar_controller.js +34 -0
- data/lib/generators/view_primitives/add/templates/menubar/menubar_menu_component.rb.tt +34 -0
- data/lib/generators/view_primitives/add/templates/navbar/navbar_component.rb.tt +90 -0
- data/lib/generators/view_primitives/add/templates/navbar/navbar_controller.js +11 -0
- data/lib/generators/view_primitives/add/templates/navigation_menu/navigation_menu_component.rb.tt +132 -0
- data/lib/generators/view_primitives/add/templates/navigation_menu/navigation_menu_controller.js +25 -0
- data/lib/generators/view_primitives/add/templates/number_input/number_input_component.rb.tt +34 -0
- data/lib/generators/view_primitives/add/templates/pagination/pagination_component.rb.tt +97 -0
- data/lib/generators/view_primitives/add/templates/picture/picture_component.rb.tt +63 -0
- data/lib/generators/view_primitives/add/templates/popover/popover_component.rb.tt +56 -0
- data/lib/generators/view_primitives/add/templates/popover/popover_controller.js +17 -0
- data/lib/generators/view_primitives/add/templates/progress/progress_component.rb.tt +28 -0
- data/lib/generators/view_primitives/add/templates/qr_code/qr_code_component.rb.tt +39 -0
- data/lib/generators/view_primitives/add/templates/radio_group/radio_group_component.rb.tt +51 -0
- data/lib/generators/view_primitives/add/templates/range/range_component.rb.tt +40 -0
- data/lib/generators/view_primitives/add/templates/rating/rating_component.rb.tt +42 -0
- data/lib/generators/view_primitives/add/templates/rating_input/rating_controller.js +47 -0
- data/lib/generators/view_primitives/add/templates/rating_input/rating_input_component.rb.tt +79 -0
- data/lib/generators/view_primitives/add/templates/resizable/resizable_component.rb.tt +91 -0
- data/lib/generators/view_primitives/add/templates/resizable/resizable_controller.js +38 -0
- data/lib/generators/view_primitives/add/templates/scroll_area/scroll_area_component.rb.tt +41 -0
- data/lib/generators/view_primitives/add/templates/search_input/search_input_component.rb.tt +50 -0
- data/lib/generators/view_primitives/add/templates/select/select_component.rb.tt +45 -0
- data/lib/generators/view_primitives/add/templates/separator/separator_component.rb.tt +25 -0
- data/lib/generators/view_primitives/add/templates/sheet/sheet_component.rb.tt +78 -0
- data/lib/generators/view_primitives/add/templates/sheet/sheet_controller.js +15 -0
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_component.rb.tt +169 -0
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_controller.js +11 -0
- data/lib/generators/view_primitives/add/templates/skeleton/skeleton_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/speed_dial/speed_dial_component.rb.tt +111 -0
- data/lib/generators/view_primitives/add/templates/speed_dial/speed_dial_controller.js +22 -0
- data/lib/generators/view_primitives/add/templates/spinner/spinner_component.rb.tt +27 -0
- data/lib/generators/view_primitives/add/templates/stepper/stepper_component.rb.tt +99 -0
- data/lib/generators/view_primitives/add/templates/switch/switch_component.rb.tt +51 -0
- data/lib/generators/view_primitives/add/templates/tabs/tabs_component.html.erb +50 -0
- data/lib/generators/view_primitives/add/templates/tabs/tabs_component.rb.tt +16 -0
- data/lib/generators/view_primitives/add/templates/tabs/tabs_controller.js +26 -0
- data/lib/generators/view_primitives/add/templates/tabs/tabs_item_component.rb.tt +15 -0
- data/lib/generators/view_primitives/add/templates/textarea/textarea_component.rb.tt +24 -0
- data/lib/generators/view_primitives/add/templates/timeline/timeline_component.rb.tt +78 -0
- data/lib/generators/view_primitives/add/templates/timepicker/timepicker_component.rb.tt +140 -0
- data/lib/generators/view_primitives/add/templates/timepicker/timepicker_controller.js +92 -0
- data/lib/generators/view_primitives/add/templates/toaster/toaster_component.rb.tt +152 -0
- data/lib/generators/view_primitives/add/templates/toaster/toaster_controller.js +88 -0
- data/lib/generators/view_primitives/add/templates/toggle/toggle_component.rb.tt +41 -0
- data/lib/generators/view_primitives/add/templates/toggle/toggle_controller.js +12 -0
- data/lib/generators/view_primitives/add/templates/toggle_group/toggle_group_component.rb.tt +32 -0
- data/lib/generators/view_primitives/add/templates/toggle_group/toggle_group_controller.js +38 -0
- data/lib/generators/view_primitives/add/templates/tooltip/tooltip_component.rb.tt +42 -0
- data/lib/generators/view_primitives/add/templates/video/video_component.rb.tt +92 -0
- data/lib/generators/view_primitives/add/templates/wysiwyg/wysiwyg_component.rb.tt +88 -0
- data/lib/generators/view_primitives/add/templates/wysiwyg/wysiwyg_controller.js +40 -0
- data/lib/generators/view_primitives/components.rb +62 -0
- data/lib/generators/view_primitives/detector.rb +43 -0
- data/lib/generators/view_primitives/install/install_generator.rb +65 -0
- data/lib/generators/view_primitives/install/templates/application_component.rb.tt +5 -0
- data/lib/generators/view_primitives/install/templates/view_primitives.css +67 -0
- data/lib/generators/view_primitives/list/list_generator.rb +25 -0
- data/lib/view_primitives/class_helper.rb +11 -0
- data/lib/view_primitives/component_helper.rb +20 -0
- data/lib/view_primitives/railtie.rb +21 -0
- data/lib/view_primitives/version.rb +5 -0
- data/lib/view_primitives.rb +12 -0
- metadata +267 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
class TooltipComponent < ApplicationComponent
|
|
5
|
+
BUBBLE_BASE = "absolute z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance " \
|
|
6
|
+
"bg-foreground text-background " \
|
|
7
|
+
"opacity-0 group-hover:opacity-100 pointer-events-none whitespace-nowrap " \
|
|
8
|
+
"transition-opacity duration-200"
|
|
9
|
+
|
|
10
|
+
POSITIONS = {
|
|
11
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
|
|
12
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
|
|
13
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-2",
|
|
14
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-2"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def initialize(text:, side: :top, **html_attrs)
|
|
18
|
+
@text = text
|
|
19
|
+
@side = side.to_sym
|
|
20
|
+
@extra_class = html_attrs.delete(:class)
|
|
21
|
+
@html_attrs = html_attrs
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
content_tag(:span,
|
|
26
|
+
class: cn("relative inline-flex group", @extra_class),
|
|
27
|
+
**@html_attrs) do
|
|
28
|
+
concat content
|
|
29
|
+
concat tooltip_bubble
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def tooltip_bubble
|
|
36
|
+
content_tag(:span,
|
|
37
|
+
@text,
|
|
38
|
+
class: cn(BUBBLE_BASE, POSITIONS.fetch(@side, POSITIONS[:top])),
|
|
39
|
+
role: "tooltip")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
class VideoComponent < ApplicationComponent
|
|
5
|
+
BASE = "max-w-full"
|
|
6
|
+
|
|
7
|
+
# Add <source> elements via v.with_source(src:, type:)
|
|
8
|
+
renders_many :sources, "UI::VideoComponent::SourceComponent"
|
|
9
|
+
# Add <track> elements via v.with_track(src:, kind:, label:, srclang:)
|
|
10
|
+
renders_many :tracks, "UI::VideoComponent::TrackComponent"
|
|
11
|
+
|
|
12
|
+
# poster: URL of the preview image shown before playback
|
|
13
|
+
# controls: show native browser controls (default: true)
|
|
14
|
+
# autoplay: start playing automatically — requires muted: true
|
|
15
|
+
# muted: mute the audio track
|
|
16
|
+
# loop: loop playback
|
|
17
|
+
# preload: :auto | :metadata (default) | :none
|
|
18
|
+
# playsinline: play inline on iOS instead of fullscreen
|
|
19
|
+
# width / height: explicit dimensions (prefer CSS or Aspect Ratio)
|
|
20
|
+
def initialize(poster: nil, controls: true, autoplay: false, muted: false,
|
|
21
|
+
loop: false, preload: :metadata, playsinline: true,
|
|
22
|
+
width: nil, height: nil, **html_attrs)
|
|
23
|
+
@poster = poster
|
|
24
|
+
@controls = controls
|
|
25
|
+
@autoplay = autoplay
|
|
26
|
+
@muted = muted
|
|
27
|
+
@loop = loop
|
|
28
|
+
@preload = preload
|
|
29
|
+
@playsinline = playsinline
|
|
30
|
+
@width = width
|
|
31
|
+
@height = height
|
|
32
|
+
@extra_class = html_attrs.delete(:class)
|
|
33
|
+
@html_attrs = html_attrs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call
|
|
37
|
+
attrs = { class: cn(BASE, @extra_class), preload: @preload }
|
|
38
|
+
attrs[:poster] = @poster if @poster
|
|
39
|
+
attrs[:controls] = true if @controls
|
|
40
|
+
attrs[:autoplay] = true if @autoplay
|
|
41
|
+
attrs[:muted] = true if @muted || @autoplay
|
|
42
|
+
attrs[:loop] = true if @loop
|
|
43
|
+
attrs[:playsinline] = true if @playsinline
|
|
44
|
+
attrs[:width] = @width if @width
|
|
45
|
+
attrs[:height] = @height if @height
|
|
46
|
+
|
|
47
|
+
content_tag(:video, **attrs, **@html_attrs) do
|
|
48
|
+
sources.each { |s| concat s }
|
|
49
|
+
tracks.each { |t| concat t }
|
|
50
|
+
concat content if content?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Represents a <source> element inside <video>.
|
|
55
|
+
# v.with_source(src: "video.mp4", type: "video/mp4")
|
|
56
|
+
class SourceComponent < ApplicationComponent
|
|
57
|
+
def initialize(src:, type:, **html_attrs)
|
|
58
|
+
@src = src
|
|
59
|
+
@type = type
|
|
60
|
+
@html_attrs = html_attrs
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call
|
|
64
|
+
tag.source(src: @src, type: @type, **@html_attrs)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Represents a <track> element (captions, subtitles, chapters).
|
|
69
|
+
# v.with_track(src: "captions.vtt", kind: :subtitles, label: "English", srclang: "en")
|
|
70
|
+
# kind: :subtitles | :captions | :descriptions | :chapters | :metadata
|
|
71
|
+
class TrackComponent < ApplicationComponent
|
|
72
|
+
KINDS = %i[subtitles captions descriptions chapters metadata].freeze
|
|
73
|
+
|
|
74
|
+
def initialize(src:, kind: :subtitles, label: nil, srclang: nil, default: false, **html_attrs)
|
|
75
|
+
@src = src
|
|
76
|
+
@kind = KINDS.include?(kind.to_sym) ? kind.to_sym : :subtitles
|
|
77
|
+
@label = label
|
|
78
|
+
@srclang = srclang
|
|
79
|
+
@default = default
|
|
80
|
+
@html_attrs = html_attrs
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def call
|
|
84
|
+
attrs = { src: @src, kind: @kind }
|
|
85
|
+
attrs[:label] = @label if @label
|
|
86
|
+
attrs[:srclang] = @srclang if @srclang
|
|
87
|
+
attrs[:default] = true if @default
|
|
88
|
+
tag.track(**attrs, **@html_attrs)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UI
|
|
4
|
+
class WysiwygComponent < ApplicationComponent
|
|
5
|
+
# Rich-text editor wrapper — picks between Trix and Quill via adapter:.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ui :wysiwyg, name: "body" # Trix (default)
|
|
9
|
+
# ui :wysiwyg, name: "body", adapter: :quill
|
|
10
|
+
# ui :wysiwyg, name: "body", adapter: :quill,
|
|
11
|
+
# placeholder: "Write something...", height: 400
|
|
12
|
+
#
|
|
13
|
+
# Trix setup (ActionText):
|
|
14
|
+
# bundle add actiontext
|
|
15
|
+
# rails action_text:install
|
|
16
|
+
#
|
|
17
|
+
# Quill setup:
|
|
18
|
+
# # config/importmap.rb
|
|
19
|
+
# pin "quill", to: "https://esm.sh/quill@2"
|
|
20
|
+
# # In your CSS entry point:
|
|
21
|
+
# @import url("https://esm.sh/quill@2/dist/quill.snow.css");
|
|
22
|
+
#
|
|
23
|
+
# Options:
|
|
24
|
+
# name: form field name (required)
|
|
25
|
+
# adapter: :trix (default) | :quill
|
|
26
|
+
# value: initial HTML content
|
|
27
|
+
# placeholder: placeholder text
|
|
28
|
+
# toolbar: show editor toolbar (default: true)
|
|
29
|
+
# height: editor content area height in px (default: 200; Quill only)
|
|
30
|
+
|
|
31
|
+
ADAPTERS = %w[trix quill].freeze
|
|
32
|
+
|
|
33
|
+
WRAPPER_CLS = "rounded-md border border-input bg-background shadow-xs"
|
|
34
|
+
|
|
35
|
+
def initialize(name:, adapter: :trix, value: nil, placeholder: nil,
|
|
36
|
+
toolbar: true, height: 200, **html_attrs)
|
|
37
|
+
@name = name
|
|
38
|
+
@adapter = ADAPTERS.include?(adapter.to_s) ? adapter.to_s : "trix"
|
|
39
|
+
@value = value
|
|
40
|
+
@placeholder = placeholder
|
|
41
|
+
@toolbar = toolbar
|
|
42
|
+
@height = height
|
|
43
|
+
@extra_class = html_attrs.delete(:class)
|
|
44
|
+
@html_attrs = html_attrs
|
|
45
|
+
@input_id = "wysiwyg-#{SecureRandom.hex(4)}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def call
|
|
49
|
+
content_tag(:div, class: cn(WRAPPER_CLS, @extra_class)) do
|
|
50
|
+
@adapter == "quill" ? quill_markup : trix_markup
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def trix_markup
|
|
57
|
+
safe_join([
|
|
58
|
+
tag.input(type: "hidden", id: @input_id, name: @name, value: @value),
|
|
59
|
+
tag.send(:"trix-editor",
|
|
60
|
+
input: @input_id,
|
|
61
|
+
placeholder: @placeholder,
|
|
62
|
+
class: "trix-content min-h-[200px] px-3 py-2 text-sm focus:outline-none")
|
|
63
|
+
])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def quill_markup
|
|
67
|
+
content_tag(:div,
|
|
68
|
+
data: {
|
|
69
|
+
controller: "wysiwyg",
|
|
70
|
+
wysiwyg_adapter_value: "quill",
|
|
71
|
+
wysiwyg_placeholder_value: @placeholder.to_s,
|
|
72
|
+
wysiwyg_height_value: @height,
|
|
73
|
+
wysiwyg_toolbar_value: @toolbar
|
|
74
|
+
}) do
|
|
75
|
+
safe_join([
|
|
76
|
+
tag.div(
|
|
77
|
+
data: { wysiwyg_target: "editor" },
|
|
78
|
+
style: "height: #{@height}px"),
|
|
79
|
+
tag.input(
|
|
80
|
+
type: "hidden",
|
|
81
|
+
name: @name,
|
|
82
|
+
value: @value,
|
|
83
|
+
data: { wysiwyg_target: "input" })
|
|
84
|
+
])
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Quill adapter — requires Quill v2 in your importmap:
|
|
2
|
+
// pin "quill", to: "https://esm.sh/quill@2"
|
|
3
|
+
// Also add Quill's stylesheet to your CSS entry point:
|
|
4
|
+
// @import url("https://esm.sh/quill@2/dist/quill.snow.css");
|
|
5
|
+
import { Controller } from "@hotwired/stimulus"
|
|
6
|
+
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
static targets = ["editor", "input"]
|
|
9
|
+
static values = {
|
|
10
|
+
adapter: { type: String, default: "trix" },
|
|
11
|
+
placeholder: { type: String, default: "" },
|
|
12
|
+
height: { type: Number, default: 200 },
|
|
13
|
+
toolbar: { type: Boolean, default: true }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#quill = null
|
|
17
|
+
|
|
18
|
+
connect() {
|
|
19
|
+
if (this.adapterValue === "quill") this.#initQuill()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
disconnect() {
|
|
23
|
+
this.#quill = null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async #initQuill() {
|
|
27
|
+
const { default: Quill } = await import("quill")
|
|
28
|
+
this.#quill = new Quill(this.editorTarget, {
|
|
29
|
+
theme: "snow",
|
|
30
|
+
placeholder: this.placeholderValue,
|
|
31
|
+
modules: { toolbar: this.toolbarValue }
|
|
32
|
+
})
|
|
33
|
+
if (this.inputTarget.value) {
|
|
34
|
+
this.#quill.root.innerHTML = this.inputTarget.value
|
|
35
|
+
}
|
|
36
|
+
this.#quill.on("text-change", () => {
|
|
37
|
+
this.inputTarget.value = this.#quill.root.innerHTML
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ViewPrimitives
|
|
4
|
+
module Generators
|
|
5
|
+
module Components
|
|
6
|
+
TEMPLATE_ROOT = File.expand_path("add/templates", __dir__)
|
|
7
|
+
|
|
8
|
+
# Stimulus controllers not colocated with the component template directory.
|
|
9
|
+
EXTRA_STIMULUS = {
|
|
10
|
+
"alert_dialog" => {source: "dialog/dialog_controller.js", name: "dialog"}
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
# Post-install instructions for components that require external dependencies.
|
|
14
|
+
SETUP_NOTES = {
|
|
15
|
+
"chart" => <<~TEXT,
|
|
16
|
+
Chart requires Chart.js. Add it to your importmap:
|
|
17
|
+
|
|
18
|
+
# config/importmap.rb
|
|
19
|
+
pin "chart.js", to: "https://esm.sh/chart.js@4"
|
|
20
|
+
|
|
21
|
+
Then use the component:
|
|
22
|
+
|
|
23
|
+
ui :chart, type: :bar,
|
|
24
|
+
labels: ["Jan", "Feb", "Mar"],
|
|
25
|
+
datasets: [{ label: "Revenue", data: [100, 200, 150] }]
|
|
26
|
+
TEXT
|
|
27
|
+
"wysiwyg" => <<~TEXT
|
|
28
|
+
WYSIWYG defaults to Trix (adapter: :trix). To use Trix, install ActionText:
|
|
29
|
+
|
|
30
|
+
bundle add actiontext
|
|
31
|
+
rails action_text:install
|
|
32
|
+
|
|
33
|
+
To use Quill (adapter: :quill), add it to your importmap:
|
|
34
|
+
|
|
35
|
+
# config/importmap.rb
|
|
36
|
+
pin "quill", to: "https://esm.sh/quill@2"
|
|
37
|
+
|
|
38
|
+
Also add Quill's stylesheet to your CSS entry point:
|
|
39
|
+
|
|
40
|
+
@import url("https://esm.sh/quill@2/dist/quill.snow.css");
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
|
|
44
|
+
ui :wysiwyg, name: "body"
|
|
45
|
+
ui :wysiwyg, name: "body", adapter: :quill, placeholder: "Write something..."
|
|
46
|
+
TEXT
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
def self.supported
|
|
50
|
+
@supported ||= Dir.children(TEMPLATE_ROOT).sort.freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.primary_path(component)
|
|
54
|
+
"app/components/ui/#{component}_component.rb"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.installed?(component, root)
|
|
58
|
+
File.exist?(File.join(root, primary_path(component)))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ViewPrimitives
|
|
4
|
+
module Generators
|
|
5
|
+
module Detector
|
|
6
|
+
TAILWIND_ENTRIES = [
|
|
7
|
+
{path: "app/assets/tailwind/application.css", stylesheets: "app/assets/stylesheets"},
|
|
8
|
+
{path: "app/assets/stylesheets/application.tailwind.css", stylesheets: "app/assets/stylesheets"},
|
|
9
|
+
{path: "app/assets/builds/tailwind.css", stylesheets: "app/assets/stylesheets"},
|
|
10
|
+
{path: "app/frontend/entrypoints/application.css", stylesheets: "app/frontend/stylesheets"},
|
|
11
|
+
{path: "app/javascript/entrypoints/application.css", stylesheets: "app/javascript/stylesheets"},
|
|
12
|
+
{path: "app/javascript/application.css", stylesheets: "app/javascript/stylesheets"}
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
JS_CONTROLLER_DIRS = %w[app/javascript/controllers app/frontend/controllers].freeze
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def tailwind_entry
|
|
20
|
+
@tailwind_entry ||= TAILWIND_ENTRIES.find do |entry|
|
|
21
|
+
File.exist?(File.join(destination_root, entry[:path]))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tailwind_entry_path = tailwind_entry&.fetch(:path)
|
|
26
|
+
def css_dest_dir = tailwind_entry&.fetch(:stylesheets) || "app/assets/stylesheets"
|
|
27
|
+
def css_dest_path = "#{css_dest_dir}/view_primitives.css"
|
|
28
|
+
|
|
29
|
+
def css_import_path
|
|
30
|
+
return unless tailwind_entry_path
|
|
31
|
+
|
|
32
|
+
Pathname.new("#{css_dest_dir}/view_primitives")
|
|
33
|
+
.relative_path_from(Pathname.new(File.dirname(tailwind_entry_path))).to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def js_controllers_dir
|
|
37
|
+
@js_controllers_dir ||= JS_CONTROLLER_DIRS.find do |dir|
|
|
38
|
+
File.exist?(File.join(destination_root, dir))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../detector"
|
|
4
|
+
|
|
5
|
+
module ViewPrimitives
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
include Detector
|
|
9
|
+
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
def verify_ui_inflection
|
|
13
|
+
return if "ui/button".camelize == "UI::ButtonComponent"
|
|
14
|
+
|
|
15
|
+
say "\n Warning: ActiveSupport inflection for `UI` is not configured.", :yellow
|
|
16
|
+
say " ViewPrimitives expects `ui/button` to resolve to `UI::ButtonComponent`.", :yellow
|
|
17
|
+
say " The gem registers this automatically — restart the Rails server if you just installed.\n", :yellow
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_application_component
|
|
21
|
+
target = "app/components/application_component.rb"
|
|
22
|
+
|
|
23
|
+
if File.exist?(File.join(destination_root, target))
|
|
24
|
+
say " ApplicationComponent already exists. Add `include ViewPrimitives::ClassHelper` manually.", :yellow
|
|
25
|
+
else
|
|
26
|
+
template "application_component.rb.tt", target
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_css_variables
|
|
31
|
+
copy_file "view_primitives.css", css_dest_path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def inject_css_import
|
|
35
|
+
entry = tailwind_entry_path
|
|
36
|
+
|
|
37
|
+
unless entry
|
|
38
|
+
say "\n Could not detect a Tailwind CSS entry point.", :yellow
|
|
39
|
+
say " Add this line to your main CSS file:\n"
|
|
40
|
+
say " @import \"./view_primitives\";\n"
|
|
41
|
+
say " Common locations: app/assets/tailwind/application.css, " \
|
|
42
|
+
"app/assets/stylesheets/application.tailwind.css, app/javascript/application.css\n", :cyan
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
entry_content = File.read(File.join(destination_root, entry))
|
|
47
|
+
|
|
48
|
+
if entry_content.include?("view_primitives")
|
|
49
|
+
say " #{entry} already imports view_primitives — skipping.", :yellow
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
import_line = "@import \"#{css_import_path}\";\n"
|
|
54
|
+
|
|
55
|
+
if entry_content.include?('@import "tailwindcss"')
|
|
56
|
+
inject_into_file entry, import_line, after: "@import \"tailwindcss\"\n"
|
|
57
|
+
elsif entry_content.include?("@import 'tailwindcss'")
|
|
58
|
+
inject_into_file entry, import_line, after: "@import 'tailwindcss'\n"
|
|
59
|
+
else
|
|
60
|
+
append_to_file entry, "\n#{import_line}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@theme inline {
|
|
2
|
+
--color-background: var(--background);
|
|
3
|
+
--color-foreground: var(--foreground);
|
|
4
|
+
--color-card: var(--card);
|
|
5
|
+
--color-card-foreground: var(--card-foreground);
|
|
6
|
+
--color-popover: var(--popover);
|
|
7
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
8
|
+
--color-primary: var(--primary);
|
|
9
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
10
|
+
--color-secondary: var(--secondary);
|
|
11
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
12
|
+
--color-muted: var(--muted);
|
|
13
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
14
|
+
--color-accent: var(--accent);
|
|
15
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
16
|
+
--color-destructive: var(--destructive);
|
|
17
|
+
--color-border: var(--border);
|
|
18
|
+
--color-input: var(--input);
|
|
19
|
+
--color-ring: var(--ring);
|
|
20
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
21
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
22
|
+
--radius-lg: var(--radius);
|
|
23
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:root {
|
|
27
|
+
--background: oklch(1 0 0);
|
|
28
|
+
--foreground: oklch(0.145 0 0);
|
|
29
|
+
--card: oklch(1 0 0);
|
|
30
|
+
--card-foreground: oklch(0.145 0 0);
|
|
31
|
+
--popover: oklch(1 0 0);
|
|
32
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
33
|
+
--primary: oklch(0.205 0 0);
|
|
34
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
35
|
+
--secondary: oklch(0.97 0 0);
|
|
36
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
37
|
+
--muted: oklch(0.97 0 0);
|
|
38
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
39
|
+
--accent: oklch(0.97 0 0);
|
|
40
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
41
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
42
|
+
--border: oklch(0.922 0 0);
|
|
43
|
+
--input: oklch(0.922 0 0);
|
|
44
|
+
--ring: oklch(0.708 0 0);
|
|
45
|
+
--radius: 0.625rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dark {
|
|
49
|
+
--background: oklch(0.145 0 0);
|
|
50
|
+
--foreground: oklch(0.985 0 0);
|
|
51
|
+
--card: oklch(0.205 0 0);
|
|
52
|
+
--card-foreground: oklch(0.985 0 0);
|
|
53
|
+
--popover: oklch(0.205 0 0);
|
|
54
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
55
|
+
--primary: oklch(0.922 0 0);
|
|
56
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
57
|
+
--secondary: oklch(0.269 0 0);
|
|
58
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
59
|
+
--muted: oklch(0.269 0 0);
|
|
60
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
61
|
+
--accent: oklch(0.269 0 0);
|
|
62
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
63
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
64
|
+
--border: oklch(1 0 0 / 10%);
|
|
65
|
+
--input: oklch(1 0 0 / 15%);
|
|
66
|
+
--ring: oklch(0.556 0 0);
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../components"
|
|
4
|
+
|
|
5
|
+
module ViewPrimitives
|
|
6
|
+
module Generators
|
|
7
|
+
class ListGenerator < Rails::Generators::Base
|
|
8
|
+
desc "List available ViewPrimitives components and whether they are installed"
|
|
9
|
+
|
|
10
|
+
def list_components
|
|
11
|
+
say "\nViewPrimitives components:\n\n", :bold
|
|
12
|
+
say "COMPONENT STATUS"
|
|
13
|
+
say "-" * 32
|
|
14
|
+
|
|
15
|
+
Components.supported.each do |component|
|
|
16
|
+
status = Components.installed?(component, destination_root) ? "installed" : "—"
|
|
17
|
+
color = (status == "installed") ? :green : :cyan
|
|
18
|
+
say format("%-18s %s", component, status), color
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
say "\nInstall: rails g view_primitives:add <name>\n", :cyan
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string/inflections"
|
|
4
|
+
|
|
5
|
+
module ViewPrimitives
|
|
6
|
+
module ComponentHelper
|
|
7
|
+
# <%= ui :button, variant: :outline do %>Click<% end %>
|
|
8
|
+
def ui(name, *, **, &)
|
|
9
|
+
klass = "UI::#{name.to_s.camelize}Component".safe_constantize
|
|
10
|
+
|
|
11
|
+
unless klass
|
|
12
|
+
raise ViewPrimitives::ComponentNotFoundError,
|
|
13
|
+
"Component `UI::#{name.to_s.camelize}Component` not found. " \
|
|
14
|
+
"Run `rails g view_primitives:add #{name}` to generate it."
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
render(klass.new(*, **), &)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ViewPrimitives
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer "view_primitives.inflections" do
|
|
6
|
+
ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym "UI" }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
generators do
|
|
10
|
+
%w[install add list].each do |gen|
|
|
11
|
+
require "generators/view_primitives/#{gen}/#{gen}_generator"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
initializer "view_primitives.component_helper" do
|
|
16
|
+
%i[action_view action_mailer].each do |hook|
|
|
17
|
+
ActiveSupport.on_load(hook) { include ViewPrimitives::ComponentHelper }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "view_primitives/version"
|
|
4
|
+
require_relative "view_primitives/class_helper"
|
|
5
|
+
require_relative "view_primitives/component_helper"
|
|
6
|
+
|
|
7
|
+
module ViewPrimitives
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
class ComponentNotFoundError < Error; end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require_relative "view_primitives/railtie" if defined?(Rails::Railtie)
|