lostcoords-ui 0.4.1 → 0.6.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +364 -96
  3. data/app/assets/tailwind/lostcoords/ui/avatar.css +6 -6
  4. data/app/assets/tailwind/lostcoords/ui/base.css +45 -0
  5. data/app/assets/tailwind/lostcoords/ui/card.css +23 -22
  6. data/app/assets/tailwind/lostcoords/ui/choice.css +39 -0
  7. data/app/assets/tailwind/lostcoords/ui/code-block.css +10 -0
  8. data/app/assets/tailwind/lostcoords/ui/{disclosure.css → collapsible.css} +6 -6
  9. data/app/assets/tailwind/lostcoords/ui/components/code-block.css +5 -0
  10. data/app/assets/tailwind/lostcoords/ui/components/collapsible.css +5 -0
  11. data/app/assets/tailwind/lostcoords/ui/components/dialog.css +9 -0
  12. data/app/assets/tailwind/lostcoords/ui/components/emoji-picker.css +5 -0
  13. data/app/assets/tailwind/lostcoords/ui/components/menu.css +5 -0
  14. data/app/assets/tailwind/lostcoords/ui/components/prose.css +3 -0
  15. data/app/assets/tailwind/lostcoords/ui/components/table.css +3 -0
  16. data/app/assets/tailwind/lostcoords/ui/components/toast.css +5 -0
  17. data/app/assets/tailwind/lostcoords/ui/components/tooltip.css +4 -0
  18. data/app/assets/tailwind/lostcoords/ui/components.css +12 -0
  19. data/app/assets/tailwind/lostcoords/ui/copy-button.css +9 -9
  20. data/app/assets/tailwind/lostcoords/ui/core.css +21 -0
  21. data/app/assets/tailwind/lostcoords/ui/dialog.css +17 -17
  22. data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +86 -88
  23. data/app/assets/tailwind/lostcoords/ui/field.css +2 -2
  24. data/app/assets/tailwind/lostcoords/ui/glass.css +9 -8
  25. data/app/assets/tailwind/lostcoords/ui/layout.css +2 -2
  26. data/app/assets/tailwind/lostcoords/ui/popover.css +14 -14
  27. data/app/assets/tailwind/lostcoords/ui/prose.css +66 -0
  28. data/app/assets/tailwind/lostcoords/ui/sheet.css +62 -63
  29. data/app/assets/tailwind/lostcoords/ui/squircle.css +7 -27
  30. data/app/assets/tailwind/lostcoords/ui/table.css +51 -0
  31. data/app/assets/tailwind/lostcoords/ui/theme.css +209 -225
  32. data/app/assets/tailwind/lostcoords/ui/toast.css +23 -0
  33. data/app/assets/tailwind/lostcoords/ui/tooltip.css +51 -0
  34. data/app/assets/tailwind/lostcoords/ui/typography.css +31 -61
  35. data/app/assets/tailwind/lostcoords/ui/ui.css +2 -21
  36. data/app/assets/tailwind/lostcoords/ui/utilities.css +14 -22
  37. data/app/helpers/lostcoords/ui/badge_helper.rb +67 -110
  38. data/app/helpers/lostcoords/ui/button_helper.rb +44 -35
  39. data/app/helpers/lostcoords/ui/card_helper.rb +9 -52
  40. data/app/helpers/lostcoords/ui/choice_helper.rb +87 -0
  41. data/app/helpers/lostcoords/ui/code_block_helper.rb +22 -0
  42. data/app/helpers/lostcoords/ui/collapsible_helper.rb +9 -0
  43. data/app/helpers/lostcoords/ui/copy_button_helper.rb +7 -7
  44. data/app/helpers/lostcoords/ui/description_list_helper.rb +12 -0
  45. data/app/helpers/lostcoords/ui/dialog_helper.rb +15 -17
  46. data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +6 -0
  47. data/app/helpers/lostcoords/ui/feedback_helper.rb +25 -6
  48. data/app/helpers/lostcoords/ui/form_helper.rb +36 -59
  49. data/app/helpers/lostcoords/ui/glass_helper.rb +7 -7
  50. data/app/helpers/lostcoords/ui/layout_helper.rb +13 -11
  51. data/app/helpers/lostcoords/ui/menu_helper.rb +45 -10
  52. data/app/helpers/lostcoords/ui/navigation_helper.rb +144 -0
  53. data/app/helpers/lostcoords/ui/primitives_helper.rb +39 -61
  54. data/app/helpers/lostcoords/ui/reading_helper.rb +2 -2
  55. data/app/helpers/lostcoords/ui/render_attributes_helper.rb +17 -1
  56. data/app/helpers/lostcoords/ui/sheet_helper.rb +7 -45
  57. data/app/helpers/lostcoords/ui/structures_helper.rb +26 -125
  58. data/app/helpers/lostcoords/ui/table_helper.rb +11 -0
  59. data/app/helpers/lostcoords/ui/tailwind_helper.rb +13 -4
  60. data/app/helpers/lostcoords/ui/toast_helper.rb +22 -0
  61. data/app/helpers/lostcoords/ui/tooltip_helper.rb +17 -0
  62. data/app/helpers/lostcoords/ui/typography_helper.rb +41 -11
  63. data/app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js +23 -0
  64. data/app/javascript/lostcoords/ui/controllers/{disclosure_controller.js → lc_collapsible_controller.js} +4 -4
  65. data/app/javascript/lostcoords/ui/controllers/{confirm_dialog_controller.js → lc_confirm_dialog_controller.js} +4 -4
  66. data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js} +1 -1
  67. data/app/javascript/lostcoords/ui/controllers/{emoji_picker_controller.js → lc_emoji_picker_controller.js} +38 -10
  68. data/app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js +12 -0
  69. data/app/javascript/lostcoords/ui/controllers/{row_menu_controller.js → lc_menu_controller.js} +25 -3
  70. data/app/javascript/lostcoords/ui/controllers/{ui_popover_controller.js → lc_popover_controller.js} +1 -1
  71. data/app/javascript/lostcoords/ui/controllers/{ui_selection_controller.js → lc_selection_controller.js} +7 -15
  72. data/app/javascript/lostcoords/ui/controllers/{sheet_controller.js → lc_sheet_controller.js} +17 -17
  73. data/app/javascript/lostcoords/ui/controllers/lc_shortcut_controller.js +45 -0
  74. data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +42 -0
  75. data/app/javascript/lostcoords/ui/controllers/{toast_controller.js → lc_toast_controller.js} +2 -2
  76. data/app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js +62 -0
  77. data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +54 -0
  78. data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +77 -25
  79. data/app/javascript/lostcoords/ui/helpers/context_dialog.js +6 -6
  80. data/app/javascript/lostcoords/ui/helpers/dialog.js +2 -2
  81. data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +30 -7
  82. data/app/javascript/lostcoords/ui/helpers/floating_panel.js +7 -8
  83. data/app/javascript/lostcoords/ui/helpers/menu_keys.js +19 -18
  84. data/app/javascript/lostcoords/ui/helpers/roving_focus.js +175 -0
  85. data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +4 -4
  86. data/app/javascript/lostcoords/ui/helpers/shortcuts.js +162 -0
  87. data/app/javascript/lostcoords/ui/helpers/top_layer.js +48 -10
  88. data/app/javascript/lostcoords/ui/index.js +57 -0
  89. data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +46 -46
  90. data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +3 -3
  91. data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +18 -0
  92. data/app/views/lostcoords/ui/components/_lc_collapsible.html.erb +5 -0
  93. data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +11 -8
  94. data/app/views/lostcoords/ui/components/_lc_floating_panel.html.erb +8 -0
  95. data/config/importmap.rb +10 -2
  96. data/config/locales/en.yml +23 -0
  97. data/config/locales/es.yml +23 -0
  98. data/config/locales/ja.yml +23 -0
  99. data/lib/lostcoords/ui/action_view.rb +6 -3
  100. data/lib/lostcoords/ui/builders.rb +2 -2
  101. data/lib/lostcoords/ui/form_builder.rb +126 -0
  102. data/lib/lostcoords/ui/parts.rb +154 -0
  103. data/lib/lostcoords/ui/stylesheets.rb +25 -0
  104. data/lib/lostcoords/ui/tokens.rb +24 -1
  105. data/lib/lostcoords/ui/version.rb +1 -1
  106. data/lib/tasks/lostcoords_ui.rake +11 -0
  107. metadata +54 -21
  108. data/app/assets/tailwind/lostcoords/ui/reading.css +0 -60
  109. data/app/views/lostcoords/ui/components/_form_alert.html.erb +0 -4
  110. data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +0 -5
  111. data/app/views/lostcoords/ui/components/_skeleton.html.erb +0 -4
  112. data/app/views/lostcoords/ui/components/_spinner.html.erb +0 -10
  113. data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +0 -8
  114. data/config/helper_renames.json +0 -113
  115. data/script/migrate_helpers.rb +0 -136
  116. /data/app/javascript/lostcoords/ui/controllers/{ui_copy_controller.js → lc_copy_controller.js} +0 -0
  117. /data/app/javascript/lostcoords/ui/controllers/{ui_toggle_controller.js → lc_toggle_controller.js} +0 -0
@@ -1,6 +1,29 @@
1
1
  en:
2
2
  lostcoords:
3
3
  ui:
4
+ pager:
5
+ label: "Pages"
6
+ previous: "Previous"
7
+ next: "Next"
8
+ skip_link:
9
+ label: "Skip to content"
10
+ spinner:
11
+ label: "Loading…"
12
+ dialog:
13
+ trigger: "Open dialog"
14
+ cancel: "Cancel"
15
+ close: "Close %{title}"
16
+ menu:
17
+ label: "Actions"
18
+ popover:
19
+ trigger: "Open popover"
20
+ breadcrumb:
21
+ label: "Breadcrumb"
22
+ code_block:
23
+ label: "Source code"
24
+ copy: "Copy %{label}"
25
+ expand: "Show all code"
26
+ collapse: "Show less code"
4
27
  copy_button:
5
28
  label: "Copy"
6
29
  copied: "Copied"
@@ -1,6 +1,29 @@
1
1
  es:
2
2
  lostcoords:
3
3
  ui:
4
+ pager:
5
+ label: "Páginas"
6
+ previous: "Anterior"
7
+ next: "Siguiente"
8
+ skip_link:
9
+ label: "Saltar al contenido"
10
+ spinner:
11
+ label: "Cargando…"
12
+ dialog:
13
+ trigger: "Abrir diálogo"
14
+ cancel: "Cancelar"
15
+ close: "Cerrar %{title}"
16
+ menu:
17
+ label: "Acciones"
18
+ popover:
19
+ trigger: "Abrir panel"
20
+ breadcrumb:
21
+ label: "Ruta de navegación"
22
+ code_block:
23
+ label: "Código fuente"
24
+ copy: "Copiar %{label}"
25
+ expand: "Mostrar todo el código"
26
+ collapse: "Mostrar menos código"
4
27
  copy_button:
5
28
  label: "Copiar"
6
29
  copied: "Copiado"
@@ -1,6 +1,29 @@
1
1
  ja:
2
2
  lostcoords:
3
3
  ui:
4
+ pager:
5
+ label: "ページ"
6
+ previous: "前へ"
7
+ next: "次へ"
8
+ skip_link:
9
+ label: "本文へ移動"
10
+ spinner:
11
+ label: "読み込み中…"
12
+ dialog:
13
+ trigger: "ダイアログを開く"
14
+ cancel: "キャンセル"
15
+ close: "%{title}を閉じる"
16
+ menu:
17
+ label: "操作"
18
+ popover:
19
+ trigger: "パネルを開く"
20
+ breadcrumb:
21
+ label: "パンくずリスト"
22
+ code_block:
23
+ label: "ソースコード"
24
+ copy: "%{label}をコピー"
25
+ expand: "コードをすべて表示"
26
+ collapse: "コードを折りたたむ"
4
27
  copy_button:
5
28
  label: "コピー"
6
29
  copied: "コピー済み"
@@ -3,8 +3,11 @@ require "action_view"
3
3
  require "active_support/core_ext/hash"
4
4
  require "active_support/core_ext/object/blank"
5
5
  require "tailwind_merge"
6
- require "lostcoords/ui/resources"
7
- require "lostcoords/ui/icons"
8
- require "lostcoords/ui/helpers"
6
+ require_relative "resources"
7
+ require_relative "icons"
8
+ require_relative "parts"
9
+ require_relative "form_builder"
10
+ require_relative "helpers"
11
+ require_relative "stylesheets"
9
12
 
10
13
  Lostcoords::Ui.load_translations!
@@ -10,8 +10,8 @@ module Lostcoords::Ui
10
10
 
11
11
  def component?
12
12
  !name.to_s.end_with?("_class") &&
13
- !%i[lc_tw lc_sheet_data lc_form_alert_icon].include?(name) &&
14
- helper != "EmojiPickerHelper"
13
+ !%i[lc_tw lc_sheet_data lc_shortcut_data lc_alert_icon].include?(name) &&
14
+ (helper != "EmojiPickerHelper" || name == :lc_emoji_picker)
15
15
  end
16
16
  end
17
17
 
@@ -0,0 +1,126 @@
1
+ module Lostcoords::Ui
2
+ # Rails form builder that renders the library's controls and connects model errors to them.
3
+ #
4
+ # <%= lc_form_with model: @project do |form| %>
5
+ # <%= form.field :name, hint: "Shown to members" %>
6
+ # <%= form.field :email, as: :email_field %>
7
+ # <%= form.switch :public %>
8
+ # <%= form.submit %>
9
+ # <% end %>
10
+ #
11
+ # Every standard helper keeps its Rails signature. A symbol `size:` (:sm, :md, :lg) picks the control
12
+ # size; an integer still sets the HTML size attribute.
13
+ class FormBuilder < ActionView::Helpers::FormBuilder
14
+ INPUTS = %i[
15
+ text_field email_field password_field telephone_field phone_field url_field search_field number_field
16
+ date_field time_field datetime_field datetime_local_field month_field week_field
17
+ ].freeze
18
+
19
+ INPUTS.each do |selector|
20
+ define_method(selector) do |method, options = {}|
21
+ size = control_size!(options)
22
+ super(method, control_options(method, options, "input", @template.lc_input_class(size: size)))
23
+ end
24
+ end
25
+
26
+ def text_area(method, options = {})
27
+ super(method, control_options(method, options, "textarea", @template.lc_textarea_class))
28
+ end
29
+ alias textarea text_area
30
+
31
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
32
+ size = control_size!(html_options)
33
+ super(method, choices, options, control_options(method, html_options, "select", @template.lc_select_class(size: size)), &block)
34
+ end
35
+
36
+ SELECT_OPTIONS = %i[include_blank prompt selected disabled].freeze
37
+
38
+ alias_method :rails_check_box, :check_box
39
+ private :rails_check_box
40
+
41
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
42
+ super(method, control_options(method, options, "checkbox-input", @template.lc_checkbox_class), checked_value, unchecked_value)
43
+ end
44
+ alias checkbox check_box
45
+
46
+ def radio_button(method, tag_value, options = {})
47
+ super(method, tag_value, control_options(method, options, "radio-input", @template.lc_radio_class))
48
+ end
49
+
50
+ # A checkbox drawn as a switch, with its label beside it.
51
+ def switch(method, label: nil, hint: nil, checked_value: "1", unchecked_value: "0", **options)
52
+ input = rails_check_box(method, control_options(method, { role: "switch" }.merge(options), "switch-input", @template.lc_switch_class, described: true), checked_value, unchecked_value)
53
+ choice_label(method, label, hint, "switch", input)
54
+ end
55
+
56
+ # A checkbox with its label beside it.
57
+ def choice(method, label: nil, hint: nil, checked_value: "1", unchecked_value: "0", **options)
58
+ input = rails_check_box(method, control_options(method, options, "checkbox-input", @template.lc_checkbox_class, described: true), checked_value, unchecked_value)
59
+ choice_label(method, label, hint, "checkbox", input)
60
+ end
61
+
62
+ def label(method, text = nil, options = {}, &block)
63
+ if text.is_a?(Hash)
64
+ options = text
65
+ text = nil
66
+ end
67
+ super(method, text, options.merge(class: @template.lc_field_label_class(extra: options[:class])), &block)
68
+ end
69
+
70
+ def submit(value = nil, options = {})
71
+ if value.is_a?(Hash)
72
+ options = value
73
+ value = nil
74
+ end
75
+ variant = options.delete(:variant) || :primary
76
+ size = options.delete(:size) || :md
77
+ data = { slot: "button", variant: variant, size: size }.merge(options.delete(:data) || {})
78
+ super(value, options.merge(class: @template.lc_button_class(variant: variant, size: size, extra: options[:class]), data: data))
79
+ end
80
+
81
+ # Label, control, hint and the attribute's errors in one field. `as:` names the control helper;
82
+ # `as: :select` takes `choices:` and Rails' select options such as `include_blank:`. A block renders
83
+ # a custom control and receives its attributes to splat with `**control`.
84
+ def field(method, label: nil, hint: nil, as: :text_field, choices: nil, **options, &block)
85
+ @template.lc_field(label: label || human_name(method), id: field_id(method), hint: hint, error: errors_for(method)) do |control|
86
+ next @template.capture(control, &block) if block
87
+
88
+ attributes = @template.send(:lc_attributes, options.except(*SELECT_OPTIONS), control.to_hash.except(:id))
89
+ if as.to_sym == :select
90
+ select(method, choices, options.slice(*SELECT_OPTIONS), attributes)
91
+ else
92
+ public_send(as, method, attributes.merge(options.slice(*SELECT_OPTIONS)))
93
+ end
94
+ end
95
+ end
96
+
97
+ private
98
+ def control_size!(options)
99
+ options[:size].is_a?(Symbol) || options[:size].is_a?(String) && options[:size].match?(/\A[a-z]+\z/) ? options.delete(:size).to_sym : :md
100
+ end
101
+
102
+ # described: points aria-describedby at the error a choice label renders, as lc_field does for its control.
103
+ def control_options(method, options, slot, classes, described: false)
104
+ options = options.dup
105
+ aria = (options.delete(:aria) || {}).dup
106
+ aria[:invalid] = true if errors_for(method).any? && !aria.key?(:invalid)
107
+ aria[:describedby] = [ aria[:describedby], "#{field_id(method)}-error" ].compact.join(" ") if described && errors_for(method).any?
108
+ data = { slot: slot }.merge(options.delete(:data) || {})
109
+ options.merge(class: @template.lc_tw(classes, options[:class]), aria: aria, data: data)
110
+ end
111
+
112
+ def choice_label(method, text, hint, slot, input)
113
+ @template.send(:lc_choice_label, slot, text || human_name(method), hint, input, errors: errors_for(method), error_id: "#{field_id(method)}-error", for: field_id(method))
114
+ end
115
+
116
+ def errors_for(method)
117
+ return [] unless object.respond_to?(:errors)
118
+
119
+ object.errors.full_messages_for(method)
120
+ end
121
+
122
+ def human_name(method)
123
+ object.class.respond_to?(:human_attribute_name) ? object.class.human_attribute_name(method) : method.to_s.humanize
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,154 @@
1
+ module Lostcoords::Ui
2
+ # Builders yielded by compound components, so their parts are scoped and discoverable:
3
+ #
4
+ # <%= lc_card do |card| %>
5
+ # <%= card.header title: "Members", description: "People in this workspace" %>
6
+ # <%= card.content do %>…<% end %>
7
+ # <%= card.footer do %>…<% end %>
8
+ # <% end %>
9
+ #
10
+ # Every part carries data-slot, so a context can style it without matching classes.
11
+ module Parts
12
+ class Base
13
+ def initialize(view)
14
+ @view = view
15
+ end
16
+
17
+ private
18
+ attr_reader :view
19
+
20
+ def merge(defaults, attributes)
21
+ view.send(:lc_attributes, defaults, attributes)
22
+ end
23
+
24
+ def slot(name, attributes, defaults = {})
25
+ merge(defaults.merge(data: { slot: name }), attributes)
26
+ end
27
+
28
+ def part(tag_name, name, classes, attributes, &block)
29
+ view.content_tag(tag_name, slot(name, attributes, class: classes), &block)
30
+ end
31
+ end
32
+
33
+ class Card < Base
34
+ def header(title: nil, description: nil, level: 3, **attributes, &actions)
35
+ heading = [ (self.title(title, level: level) if title), (self.description(description) if description) ].compact
36
+ action = view.capture(&actions) if actions
37
+ part(:header, "card-header", "flex min-w-0 items-start justify-between gap-content", attributes) do
38
+ view.safe_join([
39
+ (view.tag.div(view.safe_join(heading), class: "flex min-w-0 flex-col gap-tight", data: { slot: "card-heading" }) if heading.any?),
40
+ (view.tag.div(action, class: "flex shrink-0 items-center gap-related", data: { slot: "card-action" }) if action.present?)
41
+ ].compact)
42
+ end
43
+ end
44
+
45
+ def title(content = nil, level: 3, **attributes, &block)
46
+ view.lc_heading(content, level: level, size: :subheading, **slot("card-title", attributes), &block)
47
+ end
48
+
49
+ def description(content = nil, **attributes, &block)
50
+ view.lc_text(content, size: :small, tone: :muted, **slot("card-description", attributes), &block)
51
+ end
52
+
53
+ def content(**attributes, &block)
54
+ part(:div, "card-content", "flex min-w-0 flex-col gap-content", attributes, &block)
55
+ end
56
+
57
+ def footer(**attributes, &block)
58
+ part(:div, "card-footer", "flex min-w-0 flex-wrap items-center justify-end gap-related", attributes, &block)
59
+ end
60
+ end
61
+
62
+ # Splats into a control as `**field`, so the control gets the field's id and validation attributes.
63
+ class Field < Base
64
+ attr_reader :id
65
+
66
+ def initialize(view, id, control)
67
+ super(view)
68
+ @id = id
69
+ @control = control
70
+ end
71
+
72
+ def to_hash = @control
73
+ end
74
+
75
+ class Dialog < Base
76
+ def initialize(view, id)
77
+ super(view)
78
+ @id = id
79
+ end
80
+
81
+ def footer(**attributes, &block)
82
+ part(:div, "dialog-footer", "flex flex-wrap items-center justify-end gap-related", attributes, &block)
83
+ end
84
+
85
+ def close(label = nil, variant: :secondary, **attributes)
86
+ label ||= view.send(:lc_t, "lostcoords.ui.dialog.cancel")
87
+ view.lc_button(label, variant: variant, **merge({ data: { action: "lc-dialog#close", slot: "dialog-close" } }, attributes))
88
+ end
89
+ end
90
+
91
+ class Alert < Base
92
+ def actions(**attributes, &block)
93
+ part(:div, "alert-actions", "flex flex-wrap items-center gap-related pt-tight", attributes, &block)
94
+ end
95
+ end
96
+
97
+ class Tabs < Base
98
+ Panel = Data.define(:value, :label, :content, :attributes)
99
+
100
+ attr_reader :panels
101
+
102
+ def initialize(view)
103
+ super
104
+ @panels = []
105
+ end
106
+
107
+ # Records a panel; lc_tabs renders the tab list and panels after the block.
108
+ def panel(label, value: nil, **attributes, &block)
109
+ value = (value || label).to_s
110
+ raise ArgumentError, "tab label must not be blank" if label.blank?
111
+ raise ArgumentError, "duplicate tab value: #{value}" if @panels.any? { |panel| panel.value == value }
112
+
113
+ @panels << Panel.new(value, label, (view.capture(&block) if block), attributes)
114
+ nil
115
+ end
116
+ end
117
+
118
+ class NavList < Base
119
+ def initialize(view, compact)
120
+ super(view)
121
+ @compact = compact
122
+ end
123
+
124
+ # current: marks the page the reader is on with aria-current="page".
125
+ def link(label, url, current: false, indent: false, **attributes)
126
+ view.tag.li(class: ("ps-content" if indent), data: { slot: "nav-item" }) do
127
+ view.link_to(label, url, **merge({ class: view.lc_nav_link_class(compact: @compact), aria: { current: ("page" if current) }, data: { slot: "nav-link" } }, attributes))
128
+ end
129
+ end
130
+
131
+ # The list is named with aria-label rather than an id, so the same navigation can render twice,
132
+ # as in a sidebar and a phone menu.
133
+ def group(title, **attributes, &block)
134
+ part(:li, "nav-group", "flex flex-col gap-tight not-first:pt-section", attributes) do
135
+ view.safe_join([
136
+ view.tag.span(title, class: "px-3 pb-tight text-caption font-semibold text-foreground", aria: { hidden: true }, data: { slot: "nav-group-title" }),
137
+ view.tag.ul(view.capture(&block), class: "m-0 flex list-none flex-col gap-px p-0", aria: { label: title })
138
+ ])
139
+ end
140
+ end
141
+ end
142
+
143
+ class DescriptionList < Base
144
+ def item(term, detail = nil, **attributes, &block)
145
+ part(:div, "description-item", "grid min-w-0 gap-tight py-heading sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] sm:gap-content", attributes) do
146
+ view.safe_join([
147
+ view.tag.dt(term, class: "text-label text-muted-foreground", data: { slot: "description-term" }),
148
+ view.tag.dd(block ? view.capture(&block) : detail, class: "m-0 min-w-0 text-body text-foreground [overflow-wrap:anywhere]", data: { slot: "description-detail" })
149
+ ])
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,25 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ module Lostcoords::Ui
5
+ # The Tailwind entries a host can import: the document defaults, core.css and one file per opt-in component.
6
+ module Stylesheets
7
+ ROOT = Pathname(File.expand_path("../../../app/assets/tailwind/lostcoords/ui", __dir__))
8
+
9
+ def self.entries
10
+ [ ROOT.join("base.css"), ROOT.join("core.css"), *ROOT.glob("components/*.css").sort ]
11
+ end
12
+
13
+ # tailwindcss-rails gives an engine one generated import, of engine.css. These proxies give every
14
+ # entry a stable path in the host, such as builds/tailwind/lostcoords_ui/components/dialog.css.
15
+ def self.write(builds)
16
+ directory = Pathname(builds).join("lostcoords_ui")
17
+ FileUtils.rm_rf(directory)
18
+ entries.each do |entry|
19
+ path = directory.join(entry.relative_path_from(ROOT))
20
+ FileUtils.mkdir_p(path.dirname)
21
+ path.write("/* Generated by lostcoords-ui before every Tailwind build; do not edit. */\n@import \"#{entry}\";\n")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,9 +4,32 @@ module Lostcoords::Ui
4
4
  module Tokens
5
5
  THEME_PATH = File.expand_path("../../../app/assets/tailwind/lostcoords/ui/theme.css", __dir__)
6
6
  DECLARATION = /(?<![\w-])(--[\w-]+)\s*:/
7
+ COLORS = {
8
+ "--background" => "Page background",
9
+ "--foreground" => "Main text and headings",
10
+ "--card" => "Cards and grouped content",
11
+ "--card-foreground" => "Text on cards",
12
+ "--popover" => "Menus, popovers and dialogs",
13
+ "--popover-foreground" => "Text on menus, popovers and dialogs",
14
+ "--primary" => "Primary actions, links, selected states and keyboard focus",
15
+ "--primary-foreground" => "Text and icons on a solid primary background",
16
+ "--secondary" => "Secondary buttons",
17
+ "--secondary-foreground" => "Text on secondary buttons",
18
+ "--muted" => "Subtle backgrounds: badges, code, skeletons, table headers",
19
+ "--muted-foreground" => "Secondary text, hints and placeholders",
20
+ "--accent" => "Hovered rows, menu items and inline code",
21
+ "--accent-foreground" => "Text on accent backgrounds",
22
+ "--destructive" => "Errors and destructive actions",
23
+ "--success" => "Successful actions and positive feedback",
24
+ "--warning" => "Warnings and items needing attention",
25
+ "--border" => "Dividers and outlines",
26
+ "--input" => "Input and toggle fills",
27
+ "--ring" => "Keyboard focus ring; unset, it follows --primary"
28
+ }.freeze
29
+ FONTS = %w[--font-sans --font-display --font-mono --font-japanese].freeze
7
30
 
8
31
  def self.names
9
- File.read(THEME_PATH).scan(DECLARATION).flatten.uniq.sort
32
+ (COLORS.keys + FONTS).sort
10
33
  end
11
34
  end
12
35
 
@@ -1,5 +1,5 @@
1
1
  module Lostcoords
2
2
  module Ui
3
- VERSION = "0.4.1"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -13,3 +13,14 @@ namespace :lostcoords do
13
13
  end
14
14
  end
15
15
  end
16
+
17
+ # Runs with tailwindcss-rails' own engines step, before every tailwindcss:build and tailwindcss:watch.
18
+ # Apps without tailwindcss-rails import the gem's stylesheets by path instead.
19
+ if defined?(Tailwindcss::Engines)
20
+ namespace :tailwindcss do
21
+ task :engines do
22
+ require "lostcoords/ui/stylesheets"
23
+ Lostcoords::Ui::Stylesheets.write(Rails.root.join("app/assets/builds/tailwind"))
24
+ end
25
+ end
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lostcoords-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lost Coordinates
@@ -86,19 +86,36 @@ files:
86
86
  - LICENSE
87
87
  - README.md
88
88
  - app/assets/tailwind/lostcoords/ui/avatar.css
89
+ - app/assets/tailwind/lostcoords/ui/base.css
89
90
  - app/assets/tailwind/lostcoords/ui/card.css
91
+ - app/assets/tailwind/lostcoords/ui/choice.css
92
+ - app/assets/tailwind/lostcoords/ui/code-block.css
93
+ - app/assets/tailwind/lostcoords/ui/collapsible.css
94
+ - app/assets/tailwind/lostcoords/ui/components.css
95
+ - app/assets/tailwind/lostcoords/ui/components/code-block.css
96
+ - app/assets/tailwind/lostcoords/ui/components/collapsible.css
97
+ - app/assets/tailwind/lostcoords/ui/components/dialog.css
98
+ - app/assets/tailwind/lostcoords/ui/components/emoji-picker.css
99
+ - app/assets/tailwind/lostcoords/ui/components/menu.css
100
+ - app/assets/tailwind/lostcoords/ui/components/prose.css
101
+ - app/assets/tailwind/lostcoords/ui/components/table.css
102
+ - app/assets/tailwind/lostcoords/ui/components/toast.css
103
+ - app/assets/tailwind/lostcoords/ui/components/tooltip.css
90
104
  - app/assets/tailwind/lostcoords/ui/copy-button.css
105
+ - app/assets/tailwind/lostcoords/ui/core.css
91
106
  - app/assets/tailwind/lostcoords/ui/dialog.css
92
- - app/assets/tailwind/lostcoords/ui/disclosure.css
93
107
  - app/assets/tailwind/lostcoords/ui/emoji-picker.css
94
108
  - app/assets/tailwind/lostcoords/ui/field.css
95
109
  - app/assets/tailwind/lostcoords/ui/glass.css
96
110
  - app/assets/tailwind/lostcoords/ui/layout.css
97
111
  - app/assets/tailwind/lostcoords/ui/popover.css
98
- - app/assets/tailwind/lostcoords/ui/reading.css
112
+ - app/assets/tailwind/lostcoords/ui/prose.css
99
113
  - app/assets/tailwind/lostcoords/ui/sheet.css
100
114
  - app/assets/tailwind/lostcoords/ui/squircle.css
115
+ - app/assets/tailwind/lostcoords/ui/table.css
101
116
  - app/assets/tailwind/lostcoords/ui/theme.css
117
+ - app/assets/tailwind/lostcoords/ui/toast.css
118
+ - app/assets/tailwind/lostcoords/ui/tooltip.css
102
119
  - app/assets/tailwind/lostcoords/ui/typography.css
103
120
  - app/assets/tailwind/lostcoords/ui/ui.css
104
121
  - app/assets/tailwind/lostcoords/ui/utilities.css
@@ -106,7 +123,11 @@ files:
106
123
  - app/helpers/lostcoords/ui/badge_helper.rb
107
124
  - app/helpers/lostcoords/ui/button_helper.rb
108
125
  - app/helpers/lostcoords/ui/card_helper.rb
126
+ - app/helpers/lostcoords/ui/choice_helper.rb
127
+ - app/helpers/lostcoords/ui/code_block_helper.rb
128
+ - app/helpers/lostcoords/ui/collapsible_helper.rb
109
129
  - app/helpers/lostcoords/ui/copy_button_helper.rb
130
+ - app/helpers/lostcoords/ui/description_list_helper.rb
110
131
  - app/helpers/lostcoords/ui/dialog_helper.rb
111
132
  - app/helpers/lostcoords/ui/emoji_picker_helper.rb
112
133
  - app/helpers/lostcoords/ui/feedback_helper.rb
@@ -115,46 +136,56 @@ files:
115
136
  - app/helpers/lostcoords/ui/icon_helper.rb
116
137
  - app/helpers/lostcoords/ui/layout_helper.rb
117
138
  - app/helpers/lostcoords/ui/menu_helper.rb
139
+ - app/helpers/lostcoords/ui/navigation_helper.rb
118
140
  - app/helpers/lostcoords/ui/primitives_helper.rb
119
141
  - app/helpers/lostcoords/ui/reading_helper.rb
120
142
  - app/helpers/lostcoords/ui/render_attributes_helper.rb
121
143
  - app/helpers/lostcoords/ui/sheet_helper.rb
122
144
  - app/helpers/lostcoords/ui/structures_helper.rb
145
+ - app/helpers/lostcoords/ui/table_helper.rb
123
146
  - app/helpers/lostcoords/ui/tailwind_helper.rb
147
+ - app/helpers/lostcoords/ui/toast_helper.rb
148
+ - app/helpers/lostcoords/ui/tooltip_helper.rb
124
149
  - app/helpers/lostcoords/ui/translation_helper.rb
125
150
  - app/helpers/lostcoords/ui/typography_helper.rb
126
- - app/javascript/lostcoords/ui/controllers/confirm_dialog_controller.js
127
- - app/javascript/lostcoords/ui/controllers/disclosure_controller.js
128
- - app/javascript/lostcoords/ui/controllers/emoji_picker_controller.js
129
- - app/javascript/lostcoords/ui/controllers/modal_controller.js
130
- - app/javascript/lostcoords/ui/controllers/row_menu_controller.js
131
- - app/javascript/lostcoords/ui/controllers/sheet_controller.js
132
- - app/javascript/lostcoords/ui/controllers/toast_controller.js
133
- - app/javascript/lostcoords/ui/controllers/ui_copy_controller.js
134
- - app/javascript/lostcoords/ui/controllers/ui_popover_controller.js
135
- - app/javascript/lostcoords/ui/controllers/ui_selection_controller.js
136
- - app/javascript/lostcoords/ui/controllers/ui_toggle_controller.js
151
+ - app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js
152
+ - app/javascript/lostcoords/ui/controllers/lc_collapsible_controller.js
153
+ - app/javascript/lostcoords/ui/controllers/lc_confirm_dialog_controller.js
154
+ - app/javascript/lostcoords/ui/controllers/lc_copy_controller.js
155
+ - app/javascript/lostcoords/ui/controllers/lc_dialog_controller.js
156
+ - app/javascript/lostcoords/ui/controllers/lc_emoji_picker_controller.js
157
+ - app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js
158
+ - app/javascript/lostcoords/ui/controllers/lc_menu_controller.js
159
+ - app/javascript/lostcoords/ui/controllers/lc_popover_controller.js
160
+ - app/javascript/lostcoords/ui/controllers/lc_selection_controller.js
161
+ - app/javascript/lostcoords/ui/controllers/lc_sheet_controller.js
162
+ - app/javascript/lostcoords/ui/controllers/lc_shortcut_controller.js
163
+ - app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js
164
+ - app/javascript/lostcoords/ui/controllers/lc_toast_controller.js
165
+ - app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js
166
+ - app/javascript/lostcoords/ui/controllers/lc_toggle_controller.js
167
+ - app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js
137
168
  - app/javascript/lostcoords/ui/helpers/catalog_picker.js
138
169
  - app/javascript/lostcoords/ui/helpers/context_dialog.js
139
170
  - app/javascript/lostcoords/ui/helpers/dialog.js
140
171
  - app/javascript/lostcoords/ui/helpers/dismiss_stack.js
141
172
  - app/javascript/lostcoords/ui/helpers/floating_panel.js
142
173
  - app/javascript/lostcoords/ui/helpers/menu_keys.js
174
+ - app/javascript/lostcoords/ui/helpers/roving_focus.js
143
175
  - app/javascript/lostcoords/ui/helpers/sheet_motion.js
176
+ - app/javascript/lostcoords/ui/helpers/shortcuts.js
144
177
  - app/javascript/lostcoords/ui/helpers/top_layer.js
145
178
  - app/javascript/lostcoords/ui/helpers/viewport.js
179
+ - app/javascript/lostcoords/ui/index.js
146
180
  - app/views/lostcoords/ui/components/_emoji_picker.html.erb
147
181
  - app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb
148
- - app/views/lostcoords/ui/components/_form_alert.html.erb
182
+ - app/views/lostcoords/ui/components/_lc_code_block.html.erb
183
+ - app/views/lostcoords/ui/components/_lc_collapsible.html.erb
149
184
  - app/views/lostcoords/ui/components/_lc_dialog.html.erb
150
- - app/views/lostcoords/ui/components/_lc_disclosure.html.erb
151
- - app/views/lostcoords/ui/components/_skeleton.html.erb
152
- - app/views/lostcoords/ui/components/_spinner.html.erb
153
- - app/views/lostcoords/ui/components/_ui_floating_panel.html.erb
185
+ - app/views/lostcoords/ui/components/_lc_floating_panel.html.erb
154
186
  - config/emoji_search_aliases.yml
155
187
  - config/emoji_search_names.yml
156
188
  - config/emoji_search_names_ja.yml
157
- - config/helper_renames.json
158
189
  - config/importmap.rb
159
190
  - config/locales/en.yml
160
191
  - config/locales/es.yml
@@ -164,14 +195,16 @@ files:
164
195
  - lib/lostcoords/ui/builders.rb
165
196
  - lib/lostcoords/ui/components.rb
166
197
  - lib/lostcoords/ui/engine.rb
198
+ - lib/lostcoords/ui/form_builder.rb
167
199
  - lib/lostcoords/ui/helpers.rb
168
200
  - lib/lostcoords/ui/icons.rb
201
+ - lib/lostcoords/ui/parts.rb
169
202
  - lib/lostcoords/ui/rails.rb
170
203
  - lib/lostcoords/ui/resources.rb
204
+ - lib/lostcoords/ui/stylesheets.rb
171
205
  - lib/lostcoords/ui/tokens.rb
172
206
  - lib/lostcoords/ui/version.rb
173
207
  - lib/tasks/lostcoords_ui.rake
174
- - script/migrate_helpers.rb
175
208
  - vendor/heroicons/LICENSE
176
209
  - vendor/heroicons/icons.json
177
210
  homepage: https://github.com/lostcoords/ui