view_primitives 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +57 -2
  4. data/lib/generators/view_primitives/add/add_generator.rb +8 -62
  5. data/lib/generators/view_primitives/add/templates/accordion/accordion_item_component.rb.tt +30 -11
  6. data/lib/generators/view_primitives/add/templates/alert/alert_component.rb.tt +1 -1
  7. data/lib/generators/view_primitives/add/templates/alert_dialog/alert_dialog_component.rb.tt +9 -9
  8. data/lib/generators/view_primitives/add/templates/aspect_ratio/aspect_ratio_component.rb.tt +1 -1
  9. data/lib/generators/view_primitives/add/templates/audio/audio_component.rb.tt +1 -1
  10. data/lib/generators/view_primitives/add/templates/avatar/avatar_component.rb.tt +8 -4
  11. data/lib/generators/view_primitives/add/templates/badge/badge_component.rb.tt +1 -1
  12. data/lib/generators/view_primitives/add/templates/banner/banner_component.rb.tt +6 -6
  13. data/lib/generators/view_primitives/add/templates/bottom_nav/bottom_nav_component.rb.tt +11 -4
  14. data/lib/generators/view_primitives/add/templates/breadcrumb/breadcrumb_component.rb.tt +2 -2
  15. data/lib/generators/view_primitives/add/templates/button/button_component.rb.tt +8 -5
  16. data/lib/generators/view_primitives/add/templates/button_group/button_group_component.rb.tt +5 -5
  17. data/lib/generators/view_primitives/add/templates/calendar/calendar_component.rb.tt +18 -16
  18. data/lib/generators/view_primitives/add/templates/card/card_component.rb.tt +1 -1
  19. data/lib/generators/view_primitives/add/templates/card/card_footer_component.rb.tt +1 -1
  20. data/lib/generators/view_primitives/add/templates/carousel/carousel_component.rb.tt +26 -13
  21. data/lib/generators/view_primitives/add/templates/chart/chart_component.rb.tt +10 -4
  22. data/lib/generators/view_primitives/add/templates/chart/chart_controller.js +26 -3
  23. data/lib/generators/view_primitives/add/templates/chat_bubble/chat_bubble_component.rb.tt +4 -4
  24. data/lib/generators/view_primitives/add/templates/checkbox/checkbox_component.rb.tt +1 -1
  25. data/lib/generators/view_primitives/add/templates/collapsible/collapsible_component.rb.tt +12 -5
  26. data/lib/generators/view_primitives/add/templates/combobox/combobox_component.rb.tt +3 -6
  27. data/lib/generators/view_primitives/add/templates/command/command_component.rb.tt +22 -18
  28. data/lib/generators/view_primitives/add/templates/command/command_controller.js +50 -0
  29. data/lib/generators/view_primitives/add/templates/context_menu/context_menu_component.rb.tt +9 -8
  30. data/lib/generators/view_primitives/add/templates/data_table/data_table_component.rb.tt +60 -29
  31. data/lib/generators/view_primitives/add/templates/data_table/data_table_controller.js +2 -2
  32. data/lib/generators/view_primitives/add/templates/date_picker/date_picker_component.rb.tt +8 -8
  33. data/lib/generators/view_primitives/add/templates/device_mockup/device_mockup_component.rb.tt +94 -21
  34. data/lib/generators/view_primitives/add/templates/dialog/dialog_component.rb.tt +13 -10
  35. data/lib/generators/view_primitives/add/templates/dialog/dialog_controller.js +52 -0
  36. data/lib/generators/view_primitives/add/templates/drawer/drawer_component.rb.tt +8 -7
  37. data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt +5 -6
  38. data/lib/generators/view_primitives/add/templates/embed/embed_component.rb.tt +2 -2
  39. data/lib/generators/view_primitives/add/templates/figure/figure_component.rb.tt +1 -1
  40. data/lib/generators/view_primitives/add/templates/file_input/file_input_component.rb.tt +3 -12
  41. data/lib/generators/view_primitives/add/templates/floating_label/floating_label_component.rb.tt +1 -1
  42. data/lib/generators/view_primitives/add/templates/footer/footer_component.rb.tt +5 -4
  43. data/lib/generators/view_primitives/add/templates/form_field/form_field_component.rb.tt +18 -5
  44. data/lib/generators/view_primitives/add/templates/gallery/gallery_component.rb.tt +3 -3
  45. data/lib/generators/view_primitives/add/templates/gallery/gallery_controller.js +1 -1
  46. data/lib/generators/view_primitives/add/templates/hover_card/hover_card_component.rb.tt +6 -5
  47. data/lib/generators/view_primitives/add/templates/iframe/iframe_component.rb.tt +6 -4
  48. data/lib/generators/view_primitives/add/templates/image/image_component.rb.tt +1 -1
  49. data/lib/generators/view_primitives/add/templates/indicator/indicator_component.rb.tt +5 -4
  50. data/lib/generators/view_primitives/add/templates/input/input_component.rb.tt +2 -13
  51. data/lib/generators/view_primitives/add/templates/input_otp/input_otp_component.rb.tt +22 -10
  52. data/lib/generators/view_primitives/add/templates/kbd/kbd_component.rb.tt +3 -1
  53. data/lib/generators/view_primitives/add/templates/list_group/list_group_component.rb.tt +6 -2
  54. data/lib/generators/view_primitives/add/templates/list_group/list_group_item_component.rb.tt +6 -4
  55. data/lib/generators/view_primitives/add/templates/map_area/map_area_component.rb.tt +3 -2
  56. data/lib/generators/view_primitives/add/templates/mega_menu/mega_menu_component.rb.tt +9 -9
  57. data/lib/generators/view_primitives/add/templates/menubar/menubar_component.rb.tt +5 -5
  58. data/lib/generators/view_primitives/add/templates/menubar/menubar_menu_component.rb.tt +4 -5
  59. data/lib/generators/view_primitives/add/templates/navbar/navbar_component.rb.tt +51 -11
  60. data/lib/generators/view_primitives/add/templates/navbar/navbar_controller.js +8 -3
  61. data/lib/generators/view_primitives/add/templates/navigation_menu/navigation_menu_component.rb.tt +12 -16
  62. data/lib/generators/view_primitives/add/templates/number_input/number_input_component.rb.tt +4 -11
  63. data/lib/generators/view_primitives/add/templates/pagination/pagination_component.rb.tt +4 -3
  64. data/lib/generators/view_primitives/add/templates/picture/picture_component.rb.tt +2 -1
  65. data/lib/generators/view_primitives/add/templates/popover/popover_component.rb.tt +1 -2
  66. data/lib/generators/view_primitives/add/templates/progress/progress_component.rb.tt +3 -1
  67. data/lib/generators/view_primitives/add/templates/qr_code/qr_code_component.rb.tt +1 -1
  68. data/lib/generators/view_primitives/add/templates/radio_group/radio_group_component.rb.tt +8 -5
  69. data/lib/generators/view_primitives/add/templates/range/range_component.rb.tt +2 -3
  70. data/lib/generators/view_primitives/add/templates/rating/rating_component.rb.tt +1 -1
  71. data/lib/generators/view_primitives/add/templates/rating_input/rating_controller.js +1 -1
  72. data/lib/generators/view_primitives/add/templates/rating_input/rating_input_component.rb.tt +4 -3
  73. data/lib/generators/view_primitives/add/templates/resizable/resizable_component.rb.tt +27 -15
  74. data/lib/generators/view_primitives/add/templates/scroll_area/scroll_area_component.rb.tt +10 -11
  75. data/lib/generators/view_primitives/add/templates/search_input/search_input_component.rb.tt +2 -11
  76. data/lib/generators/view_primitives/add/templates/select/select_component.rb.tt +25 -6
  77. data/lib/generators/view_primitives/add/templates/separator/separator_component.rb.tt +6 -3
  78. data/lib/generators/view_primitives/add/templates/sheet/sheet_component.rb.tt +25 -21
  79. data/lib/generators/view_primitives/add/templates/sidebar/sidebar_component.rb.tt +27 -21
  80. data/lib/generators/view_primitives/add/templates/skeleton/skeleton_component.rb.tt +1 -1
  81. data/lib/generators/view_primitives/add/templates/speed_dial/speed_dial_component.rb.tt +8 -9
  82. data/lib/generators/view_primitives/add/templates/spinner/spinner_component.rb.tt +15 -6
  83. data/lib/generators/view_primitives/add/templates/stepper/stepper_component.rb.tt +17 -16
  84. data/lib/generators/view_primitives/add/templates/switch/switch_component.rb.tt +27 -14
  85. data/lib/generators/view_primitives/add/templates/tabs/tabs_component.html.erb +13 -7
  86. data/lib/generators/view_primitives/add/templates/tags_input/tags_input_component.rb.tt +136 -0
  87. data/lib/generators/view_primitives/add/templates/tags_input/tags_input_controller.js +90 -0
  88. data/lib/generators/view_primitives/add/templates/textarea/textarea_component.rb.tt +2 -11
  89. data/lib/generators/view_primitives/add/templates/timeline/timeline_component.rb.tt +9 -7
  90. data/lib/generators/view_primitives/add/templates/timepicker/timepicker_component.rb.tt +19 -15
  91. data/lib/generators/view_primitives/add/templates/toaster/toaster_component.rb.tt +10 -10
  92. data/lib/generators/view_primitives/add/templates/toaster/toaster_controller.js +6 -6
  93. data/lib/generators/view_primitives/add/templates/toggle/toggle_component.rb.tt +10 -3
  94. data/lib/generators/view_primitives/add/templates/toggle_group/toggle_group_component.rb.tt +6 -6
  95. data/lib/generators/view_primitives/add/templates/tooltip/tooltip_component.rb.tt +7 -6
  96. data/lib/generators/view_primitives/add/templates/video/video_component.rb.tt +1 -1
  97. data/lib/generators/view_primitives/add/templates/wysiwyg/wysiwyg_component.rb.tt +9 -3
  98. data/lib/generators/view_primitives/component_copier.rb +96 -0
  99. data/lib/generators/view_primitives/components.rb +16 -2
  100. data/lib/generators/view_primitives/install/install_generator.rb +13 -3
  101. data/lib/generators/view_primitives/install/templates/application_component.rb.tt +7 -0
  102. data/lib/generators/view_primitives/install/templates/styles.rb.tt +26 -0
  103. data/lib/generators/view_primitives/install/templates/view_primitives/themes/default.css +79 -0
  104. data/lib/generators/view_primitives/install/templates/view_primitives/themes/rose.css +57 -0
  105. data/lib/generators/view_primitives/install/templates/view_primitives/tokens.css +46 -0
  106. data/lib/generators/view_primitives/install/templates/view_primitives/utilities.css +64 -0
  107. data/lib/generators/view_primitives/install/templates/view_primitives.css +6 -66
  108. data/lib/generators/view_primitives/list/list_generator.rb +3 -1
  109. data/lib/generators/view_primitives/theme/theme_generator.rb +79 -0
  110. data/lib/generators/view_primitives/update/update_generator.rb +112 -0
  111. data/lib/view_primitives/class_helper.rb +4 -1
  112. data/lib/view_primitives/railtie.rb +1 -1
  113. data/lib/view_primitives/version.rb +1 -1
  114. metadata +12 -4
  115. data/lib/generators/view_primitives/add/templates/drawer/drawer_controller.js +0 -15
  116. data/lib/generators/view_primitives/add/templates/sheet/sheet_controller.js +0 -15
@@ -2,10 +2,9 @@
2
2
 
3
3
  module UI
4
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"
5
+ BUBBLE_BASE = "pointer-events-none absolute z-50 w-fit rounded-md bg-foreground px-3 py-1.5 " \
6
+ "text-xs text-balance whitespace-nowrap text-background opacity-0 " \
7
+ "transition-opacity duration-200 group-hover:opacity-100"
9
8
 
10
9
  POSITIONS = {
11
10
  top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
@@ -23,7 +22,8 @@ module UI
23
22
 
24
23
  def call
25
24
  content_tag(:span,
26
- class: cn("relative inline-flex group", @extra_class),
25
+ class: cn("group relative inline-flex", @extra_class),
26
+ data: { slot: "tooltip" },
27
27
  **@html_attrs) do
28
28
  concat content
29
29
  concat tooltip_bubble
@@ -36,7 +36,8 @@ module UI
36
36
  content_tag(:span,
37
37
  @text,
38
38
  class: cn(BUBBLE_BASE, POSITIONS.fetch(@side, POSITIONS[:top])),
39
- role: "tooltip")
39
+ role: "tooltip",
40
+ data: { slot: "tooltip-content" })
40
41
  end
41
42
  end
42
43
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module UI
4
4
  class VideoComponent < ApplicationComponent
5
- BASE = "max-w-full"
5
+ BASE = "h-auto w-full max-w-full rounded-md"
6
6
 
7
7
  # Add <source> elements via v.with_source(src:, type:)
8
8
  renders_many :sources, "UI::VideoComponent::SourceComponent"
@@ -30,7 +30,10 @@ module UI
30
30
 
31
31
  ADAPTERS = %w[trix quill].freeze
32
32
 
33
- WRAPPER_CLS = "rounded-md border border-input bg-background shadow-xs"
33
+ WRAPPER_CLS = "rounded-md border border-input bg-transparent shadow-xs dark:bg-input/30"
34
+ TRIX_CLS = "trix-content min-h-[200px] px-3 py-2 text-sm outline-none " \
35
+ "selection:bg-primary selection:text-primary-foreground " \
36
+ "#{UI::Styles::FOCUS_RING}"
34
37
 
35
38
  def initialize(name:, adapter: :trix, value: nil, placeholder: nil,
36
39
  toolbar: true, height: 200, **html_attrs)
@@ -46,7 +49,10 @@ module UI
46
49
  end
47
50
 
48
51
  def call
49
- content_tag(:div, class: cn(WRAPPER_CLS, @extra_class)) do
52
+ content_tag(:div,
53
+ class: cn(WRAPPER_CLS, @extra_class),
54
+ data: { slot: "wysiwyg" },
55
+ **@html_attrs) do
50
56
  @adapter == "quill" ? quill_markup : trix_markup
51
57
  end
52
58
  end
@@ -59,7 +65,7 @@ module UI
59
65
  tag.send(:"trix-editor",
60
66
  input: @input_id,
61
67
  placeholder: @placeholder,
62
- class: "trix-content min-h-[200px] px-3 py-2 text-sm focus:outline-none")
68
+ class: TRIX_CLS)
63
69
  ])
64
70
  end
65
71
 
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "components"
4
+
5
+ module ViewPrimitives
6
+ module Generators
7
+ module ComponentCopier
8
+ def self.included(base)
9
+ base.no_tasks do
10
+ define_method(:template) do |source, *args, **options, &blk|
11
+ destination = args.first || options[:to]
12
+ return unless destination.nil? || confirm_overwrite(destination)
13
+
14
+ super(source, *args, **options, &blk)
15
+ end
16
+
17
+ define_method(:copy_file) do |source, *args, **options|
18
+ destination = args.first || options[:to]
19
+ return unless destination.nil? || confirm_overwrite(destination)
20
+
21
+ super(source, *args, **options)
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def copy_component(name)
29
+ dir = File.join(Components::TEMPLATE_ROOT, name)
30
+ unless File.directory?(dir)
31
+ say " Missing templates for #{name}", :red
32
+ return false
33
+ end
34
+
35
+ with_source_root(Components::TEMPLATE_ROOT) do
36
+ Dir.each_child(dir).sort.each { |file| copy_template_file(name, file) }
37
+ end
38
+ copy_extra_stimulus(name)
39
+ true
40
+ end
41
+
42
+ def copy_template_file(component, file)
43
+ relative = File.join(component, file)
44
+
45
+ case file
46
+ when /\.rb\.tt\z/
47
+ template relative, "app/components/ui/#{file.delete_suffix(".tt")}"
48
+ when /\.html\.erb\z/
49
+ copy_file relative, "app/components/ui/#{file}"
50
+ when /_controller\.js\z/
51
+ copy_js_controller("#{component}/#{file}", file.delete_suffix("_controller.js"))
52
+ end
53
+ end
54
+
55
+ def copy_extra_stimulus(name)
56
+ config = Components::EXTRA_STIMULUS[name]
57
+ return unless config
58
+
59
+ with_source_root(Components::TEMPLATE_ROOT) do
60
+ copy_js_controller(config[:source], config[:name])
61
+ end
62
+ end
63
+
64
+ def copy_js_controller(relative_source, stimulus_name)
65
+ return if stimulus_name.empty?
66
+
67
+ dir = js_controllers_dir
68
+ unless dir
69
+ say " Could not detect a JS controllers directory.", :yellow
70
+ say " Copy #{relative_source} manually and register Stimulus `#{stimulus_name}`.", :cyan
71
+ return
72
+ end
73
+
74
+ dest = "#{dir}/#{stimulus_name}_controller.js"
75
+ copy_file relative_source, dest
76
+ say " Stimulus `#{stimulus_name}` → #{dest}", :green
77
+ end
78
+
79
+ def with_source_root(path)
80
+ already_present = source_paths.include?(path)
81
+ source_paths.unshift(path) unless already_present
82
+ yield
83
+ ensure
84
+ source_paths.delete(path) unless already_present
85
+ end
86
+
87
+ def confirm_overwrite(destination)
88
+ return true unless File.exist?(File.join(destination_root, destination))
89
+ return true if options[:force]
90
+
91
+ say " #{destination} already exists.", :yellow
92
+ yes?(" Overwrite? [y/N] ")
93
+ end
94
+ end
95
+ end
96
+ end
@@ -7,9 +7,13 @@ module ViewPrimitives
7
7
 
8
8
  # Stimulus controllers not colocated with the component template directory.
9
9
  EXTRA_STIMULUS = {
10
- "alert_dialog" => {source: "dialog/dialog_controller.js", name: "dialog"}
10
+ "alert_dialog" => {source: "dialog/dialog_controller.js", name: "dialog"},
11
+ "sheet" => {source: "dialog/dialog_controller.js", name: "dialog"},
12
+ "drawer" => {source: "dialog/dialog_controller.js", name: "dialog"}
11
13
  }.freeze
12
14
 
15
+ OPTIONAL_THEMES = %w[rose].freeze
16
+
13
17
  # Post-install instructions for components that require external dependencies.
14
18
  SETUP_NOTES = {
15
19
  "chart" => <<~TEXT,
@@ -47,7 +51,9 @@ module ViewPrimitives
47
51
  }.freeze
48
52
 
49
53
  def self.supported
50
- @supported ||= Dir.children(TEMPLATE_ROOT).sort.freeze
54
+ @supported ||= Dir.children(TEMPLATE_ROOT)
55
+ .select { |e| File.directory?(File.join(TEMPLATE_ROOT, e)) }
56
+ .sort.freeze
51
57
  end
52
58
 
53
59
  def self.primary_path(component)
@@ -57,6 +63,14 @@ module ViewPrimitives
57
63
  def self.installed?(component, root)
58
64
  File.exist?(File.join(root, primary_path(component)))
59
65
  end
66
+
67
+ def self.installed(root)
68
+ supported.select { |name| installed?(name, root) }
69
+ end
70
+
71
+ def self.optional_theme_imports
72
+ OPTIONAL_THEMES
73
+ end
60
74
  end
61
75
  end
62
76
  end
@@ -9,6 +9,9 @@ module ViewPrimitives
9
9
 
10
10
  source_root File.expand_path("templates", __dir__)
11
11
 
12
+ class_option :force, type: :boolean, default: false,
13
+ desc: "Overwrite existing ApplicationComponent and CSS files"
14
+
12
15
  def verify_ui_inflection
13
16
  return if "ui/button_component".camelize == "UI::ButtonComponent"
14
17
 
@@ -19,16 +22,23 @@ module ViewPrimitives
19
22
 
20
23
  def create_application_component
21
24
  target = "app/components/application_component.rb"
25
+ exists = File.exist?(File.join(destination_root, target))
22
26
 
23
- if File.exist?(File.join(destination_root, target))
24
- say " ApplicationComponent already exists. Add `include ViewPrimitives::ClassHelper` manually.", :yellow
27
+ if exists && !options[:force]
28
+ say " ApplicationComponent already exists. Add `include ViewPrimitives::ClassHelper` and " \
29
+ "`extract_html_attrs` manually, or re-run with --force.", :yellow
25
30
  else
26
31
  template "application_component.rb.tt", target
27
32
  end
28
33
  end
29
34
 
30
- def create_css_variables
35
+ def create_ui_styles
36
+ template "styles.rb.tt", "app/components/ui/styles.rb"
37
+ end
38
+
39
+ def create_css_bundle
31
40
  copy_file "view_primitives.css", css_dest_path
41
+ directory "view_primitives", "#{css_dest_dir}/view_primitives"
32
42
  end
33
43
 
34
44
  def inject_css_import
@@ -2,4 +2,11 @@
2
2
 
3
3
  class ApplicationComponent < ViewComponent::Base
4
4
  include ViewPrimitives::ClassHelper
5
+
6
+ private
7
+
8
+ def extract_html_attrs(**html_attrs)
9
+ @extra_class = html_attrs.delete(:class)
10
+ @html_attrs = html_attrs
11
+ end
5
12
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UI
4
+ # Shared CSS primitive class names (see view_primitives/utilities.css).
5
+ # Update via: rails g view_primitives:update --skip-components
6
+ module Styles
7
+ FOCUS_RING = "vp-focus-ring"
8
+ PEER_FOCUS_RING = "vp-peer-focus-ring"
9
+ BORDER = "vp-border"
10
+ INPUT = "vp-input"
11
+ TEXTAREA = "vp-textarea"
12
+ SELECT = "vp-select"
13
+ OVERLAY = "vp-overlay"
14
+ POPOVER_PANEL = "vp-popover-panel"
15
+ MENU_ITEM = "vp-menu-item"
16
+ MENU_SEPARATOR = "block -mx-1 my-1 h-px shrink-0 border-0 bg-border"
17
+ # Form-adjacent surfaces (calendar, picker popovers)
18
+ FIELD_PANEL = "rounded-md border border-input bg-background shadow-xs"
19
+ # Date/time picker trigger button shell (add width via class:)
20
+ PICKER_TRIGGER = "inline-flex h-9 shrink-0 cursor-pointer items-center justify-start gap-2 " \
21
+ "rounded-md border border-input bg-background px-4 py-2 text-left text-sm font-normal shadow-xs " \
22
+ "transition-all outline-none hover:bg-accent hover:text-accent-foreground " \
23
+ "#{FOCUS_RING} dark:bg-input/30 dark:hover:bg-input/50 " \
24
+ "aria-expanded:border-ring has-[>svg]:px-3"
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ /* Default light and dark theme values. Override per-theme in themes/*.css. */
2
+ :root {
3
+ --radius: 0.625rem;
4
+ --background: oklch(1 0 0);
5
+ --foreground: oklch(0.145 0 0);
6
+ --card: oklch(1 0 0);
7
+ --card-foreground: oklch(0.145 0 0);
8
+ --popover: oklch(1 0 0);
9
+ --popover-foreground: oklch(0.145 0 0);
10
+ --primary: oklch(0.205 0 0);
11
+ --primary-foreground: oklch(0.985 0 0);
12
+ --secondary: oklch(0.97 0 0);
13
+ --secondary-foreground: oklch(0.205 0 0);
14
+ --muted: oklch(0.97 0 0);
15
+ --muted-foreground: oklch(0.556 0 0);
16
+ --accent: oklch(0.97 0 0);
17
+ --accent-foreground: oklch(0.205 0 0);
18
+ --destructive: oklch(0.577 0.245 27.325);
19
+ --destructive-foreground: oklch(0.97 0.01 17);
20
+ --border: oklch(0.922 0 0);
21
+ --input: oklch(0.922 0 0);
22
+ --ring: oklch(0.708 0 0);
23
+ --chart-1: oklch(0.809 0.105 251.813);
24
+ --chart-2: oklch(0.623 0.214 259.815);
25
+ --chart-3: oklch(0.546 0.245 262.881);
26
+ --chart-4: oklch(0.488 0.243 264.376);
27
+ --chart-5: oklch(0.424 0.199 265.638);
28
+ --sidebar: oklch(0.985 0 0);
29
+ --sidebar-foreground: oklch(0.145 0 0);
30
+ --sidebar-primary: oklch(0.205 0 0);
31
+ --sidebar-primary-foreground: oklch(0.985 0 0);
32
+ --sidebar-accent: oklch(0.97 0 0);
33
+ --sidebar-accent-foreground: oklch(0.205 0 0);
34
+ --sidebar-border: oklch(0.922 0 0);
35
+ --sidebar-ring: oklch(0.708 0 0);
36
+ --surface: oklch(0.98 0 0);
37
+ --surface-foreground: var(--foreground);
38
+ --selection: oklch(0.205 0 0);
39
+ --selection-foreground: oklch(1 0 0);
40
+ }
41
+
42
+ .dark {
43
+ --background: oklch(0.145 0 0);
44
+ --foreground: oklch(0.985 0 0);
45
+ --card: oklch(0.205 0 0);
46
+ --card-foreground: oklch(0.985 0 0);
47
+ --popover: oklch(0.205 0 0);
48
+ --popover-foreground: oklch(0.985 0 0);
49
+ --primary: oklch(0.922 0 0);
50
+ --primary-foreground: oklch(0.205 0 0);
51
+ --secondary: oklch(0.269 0 0);
52
+ --secondary-foreground: oklch(0.985 0 0);
53
+ --muted: oklch(0.269 0 0);
54
+ --muted-foreground: oklch(0.708 0 0);
55
+ --accent: oklch(0.371 0 0);
56
+ --accent-foreground: oklch(0.985 0 0);
57
+ --destructive: oklch(0.704 0.191 22.216);
58
+ --destructive-foreground: oklch(0.58 0.22 27);
59
+ --border: oklch(1 0 0 / 10%);
60
+ --input: oklch(1 0 0 / 15%);
61
+ --ring: oklch(0.556 0 0);
62
+ --chart-1: oklch(0.809 0.105 251.813);
63
+ --chart-2: oklch(0.623 0.214 259.815);
64
+ --chart-3: oklch(0.546 0.245 262.881);
65
+ --chart-4: oklch(0.488 0.243 264.376);
66
+ --chart-5: oklch(0.424 0.199 265.638);
67
+ --sidebar: oklch(0.205 0 0);
68
+ --sidebar-foreground: oklch(0.985 0 0);
69
+ --sidebar-primary: oklch(0.488 0.243 264.376);
70
+ --sidebar-primary-foreground: oklch(0.985 0 0);
71
+ --sidebar-accent: oklch(0.269 0 0);
72
+ --sidebar-accent-foreground: oklch(0.985 0 0);
73
+ --sidebar-border: oklch(1 0 0 / 10%);
74
+ --sidebar-ring: oklch(0.439 0 0);
75
+ --surface: oklch(0.2 0 0);
76
+ --surface-foreground: oklch(0.708 0 0);
77
+ --selection: oklch(0.922 0 0);
78
+ --selection-foreground: oklch(0.205 0 0);
79
+ }
@@ -0,0 +1,57 @@
1
+ /* Rose theme — apply with data-theme="rose" on html or a layout wrapper. */
2
+ [data-theme="rose"] {
3
+ --radius: 0.75rem;
4
+ --background: oklch(0.99 0.01 350);
5
+ --foreground: oklch(0.25 0.04 350);
6
+ --card: oklch(1 0.005 350);
7
+ --card-foreground: oklch(0.25 0.04 350);
8
+ --popover: oklch(1 0.005 350);
9
+ --popover-foreground: oklch(0.25 0.04 350);
10
+ --primary: oklch(0.55 0.2 350);
11
+ --primary-foreground: oklch(0.99 0.01 350);
12
+ --secondary: oklch(0.96 0.02 350);
13
+ --secondary-foreground: oklch(0.35 0.06 350);
14
+ --muted: oklch(0.96 0.02 350);
15
+ --muted-foreground: oklch(0.5 0.05 350);
16
+ --accent: oklch(0.94 0.03 350);
17
+ --accent-foreground: oklch(0.35 0.06 350);
18
+ --destructive: oklch(0.577 0.245 27.325);
19
+ --destructive-foreground: oklch(0.97 0.01 17);
20
+ --border: oklch(0.9 0.03 350);
21
+ --input: oklch(0.9 0.03 350);
22
+ --ring: oklch(0.55 0.2 350);
23
+ --chart-1: oklch(0.65 0.22 350);
24
+ --chart-2: oklch(0.6 0.18 10);
25
+ --chart-3: oklch(0.55 0.15 320);
26
+ --chart-4: oklch(0.7 0.12 280);
27
+ --chart-5: oklch(0.5 0.1 200);
28
+ --sidebar: oklch(0.98 0.015 350);
29
+ --sidebar-foreground: oklch(0.25 0.04 350);
30
+ --sidebar-primary: oklch(0.55 0.2 350);
31
+ --sidebar-primary-foreground: oklch(0.99 0.01 350);
32
+ --sidebar-accent: oklch(0.94 0.03 350);
33
+ --sidebar-accent-foreground: oklch(0.35 0.06 350);
34
+ --sidebar-border: oklch(0.9 0.03 350);
35
+ --sidebar-ring: oklch(0.55 0.2 350);
36
+ }
37
+
38
+ [data-theme="rose"].dark,
39
+ .dark [data-theme="rose"] {
40
+ --background: oklch(0.18 0.03 350);
41
+ --foreground: oklch(0.97 0.01 350);
42
+ --card: oklch(0.22 0.04 350);
43
+ --card-foreground: oklch(0.97 0.01 350);
44
+ --popover: oklch(0.22 0.04 350);
45
+ --popover-foreground: oklch(0.97 0.01 350);
46
+ --primary: oklch(0.72 0.18 350);
47
+ --primary-foreground: oklch(0.2 0.04 350);
48
+ --secondary: oklch(0.28 0.04 350);
49
+ --secondary-foreground: oklch(0.97 0.01 350);
50
+ --muted: oklch(0.28 0.04 350);
51
+ --muted-foreground: oklch(0.7 0.05 350);
52
+ --accent: oklch(0.32 0.05 350);
53
+ --accent-foreground: oklch(0.97 0.01 350);
54
+ --border: oklch(1 0 0 / 12%);
55
+ --input: oklch(1 0 0 / 16%);
56
+ --ring: oklch(0.72 0.18 350);
57
+ }
@@ -0,0 +1,46 @@
1
+ /* Design token bridge — maps CSS variables to Tailwind theme keys. */
2
+ @theme inline {
3
+ --color-background: var(--background);
4
+ --color-foreground: var(--foreground);
5
+ --color-card: var(--card);
6
+ --color-card-foreground: var(--card-foreground);
7
+ --color-popover: var(--popover);
8
+ --color-popover-foreground: var(--popover-foreground);
9
+ --color-primary: var(--primary);
10
+ --color-primary-foreground: var(--primary-foreground);
11
+ --color-secondary: var(--secondary);
12
+ --color-secondary-foreground: var(--secondary-foreground);
13
+ --color-muted: var(--muted);
14
+ --color-muted-foreground: var(--muted-foreground);
15
+ --color-accent: var(--accent);
16
+ --color-accent-foreground: var(--accent-foreground);
17
+ --color-destructive: var(--destructive);
18
+ --color-destructive-foreground: var(--destructive-foreground);
19
+ --color-border: var(--border);
20
+ --color-input: var(--input);
21
+ --color-ring: var(--ring);
22
+ --color-chart-1: var(--chart-1);
23
+ --color-chart-2: var(--chart-2);
24
+ --color-chart-3: var(--chart-3);
25
+ --color-chart-4: var(--chart-4);
26
+ --color-chart-5: var(--chart-5);
27
+ --color-sidebar: var(--sidebar);
28
+ --color-sidebar-foreground: var(--sidebar-foreground);
29
+ --color-sidebar-primary: var(--sidebar-primary);
30
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
31
+ --color-sidebar-accent: var(--sidebar-accent);
32
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
33
+ --color-sidebar-border: var(--sidebar-border);
34
+ --color-sidebar-ring: var(--sidebar-ring);
35
+ --color-surface: var(--surface);
36
+ --color-surface-foreground: var(--surface-foreground);
37
+ --color-selection: var(--selection);
38
+ --color-selection-foreground: var(--selection-foreground);
39
+ --radius-sm: calc(var(--radius) * 0.6);
40
+ --radius-md: calc(var(--radius) * 0.8);
41
+ --radius-lg: var(--radius);
42
+ --radius-xl: calc(var(--radius) * 1.4);
43
+ --radius-2xl: calc(var(--radius) * 1.8);
44
+ --radius-3xl: calc(var(--radius) * 2.2);
45
+ --radius-4xl: calc(var(--radius) * 2.6);
46
+ }
@@ -0,0 +1,64 @@
1
+ /* Shared component primitives — referenced from UI::Styles in Ruby components. */
2
+
3
+ @utility vp-focus-ring {
4
+ @apply outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50;
5
+ }
6
+
7
+ @utility vp-input {
8
+ @apply h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs;
9
+ @apply transition-[color,box-shadow] outline-none;
10
+ @apply selection:bg-primary selection:text-primary-foreground;
11
+ @apply file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground;
12
+ @apply placeholder:text-muted-foreground;
13
+ @apply vp-focus-ring;
14
+ @apply aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40;
15
+ @apply disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
16
+ @apply md:text-sm dark:bg-input/30;
17
+ }
18
+
19
+ @utility vp-textarea {
20
+ @apply field-sizing-content min-h-16 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs;
21
+ @apply transition-[color,box-shadow] outline-none;
22
+ @apply selection:bg-primary selection:text-primary-foreground;
23
+ @apply placeholder:text-muted-foreground;
24
+ @apply vp-focus-ring;
25
+ @apply aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40;
26
+ @apply disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
27
+ @apply md:text-sm dark:bg-input/30;
28
+ }
29
+
30
+ @utility vp-overlay {
31
+ @apply fixed inset-0 z-50 bg-black/50;
32
+ }
33
+
34
+ @utility vp-border {
35
+ @apply border border-border;
36
+ }
37
+
38
+ @utility vp-popover-panel {
39
+ @apply absolute z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-xs outline-hidden;
40
+ }
41
+
42
+ @utility vp-select {
43
+ @apply h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent px-3 py-2 pr-9 text-sm shadow-xs;
44
+ @apply transition-[color,box-shadow] outline-none;
45
+ @apply selection:bg-primary selection:text-primary-foreground;
46
+ @apply placeholder:text-muted-foreground;
47
+ @apply vp-focus-ring;
48
+ @apply aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40;
49
+ @apply disabled:pointer-events-none disabled:cursor-not-allowed;
50
+ @apply data-[size=sm]:h-8 data-[size=sm]:py-1;
51
+ @apply dark:bg-input/30 dark:hover:bg-input/50;
52
+ }
53
+
54
+ @utility vp-peer-focus-ring {
55
+ @apply peer-focus-visible:border-ring peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50;
56
+ }
57
+
58
+ @utility vp-menu-item {
59
+ @apply relative flex cursor-default items-center gap-2 whitespace-nowrap rounded-sm px-2 py-1.5 text-sm outline-hidden select-none;
60
+ @apply transition-colors;
61
+ @apply focus:bg-accent focus:text-accent-foreground;
62
+ @apply data-[disabled]:pointer-events-none data-[disabled]:opacity-50;
63
+ @apply [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
64
+ }
@@ -1,67 +1,7 @@
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
- }
1
+ /* ViewPrimitives design system entry point. */
2
+ @import "./view_primitives/tokens.css";
3
+ @import "./view_primitives/utilities.css";
4
+ @import "./view_primitives/themes/default.css";
25
5
 
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
- }
6
+ /* Optional themes — install with: rails g view_primitives:theme rose */
7
+ /* @import "./view_primitives/themes/rose.css"; */
@@ -18,7 +18,9 @@ module ViewPrimitives
18
18
  say format("%-18s %s", component, status), color
19
19
  end
20
20
 
21
- say "\nInstall: rails g view_primitives:add <name>\n", :cyan
21
+ say "\nInstall: rails g view_primitives:add <name>", :cyan
22
+ say "Update: rails g view_primitives:update", :cyan
23
+ say "Themes: rails g view_primitives:theme rose\n", :cyan
22
24
  end
23
25
  end
24
26
  end