lostcoords-ui 0.4.1
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/LICENSE +21 -0
- data/README.md +352 -0
- data/app/assets/tailwind/lostcoords/ui/avatar.css +11 -0
- data/app/assets/tailwind/lostcoords/ui/card.css +26 -0
- data/app/assets/tailwind/lostcoords/ui/copy-button.css +33 -0
- data/app/assets/tailwind/lostcoords/ui/dialog.css +60 -0
- data/app/assets/tailwind/lostcoords/ui/disclosure.css +43 -0
- data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +462 -0
- data/app/assets/tailwind/lostcoords/ui/field.css +6 -0
- data/app/assets/tailwind/lostcoords/ui/glass.css +37 -0
- data/app/assets/tailwind/lostcoords/ui/layout.css +5 -0
- data/app/assets/tailwind/lostcoords/ui/popover.css +75 -0
- data/app/assets/tailwind/lostcoords/ui/reading.css +60 -0
- data/app/assets/tailwind/lostcoords/ui/sheet.css +170 -0
- data/app/assets/tailwind/lostcoords/ui/squircle.css +32 -0
- data/app/assets/tailwind/lostcoords/ui/theme.css +255 -0
- data/app/assets/tailwind/lostcoords/ui/typography.css +81 -0
- data/app/assets/tailwind/lostcoords/ui/ui.css +21 -0
- data/app/assets/tailwind/lostcoords/ui/utilities.css +45 -0
- data/app/assets/tailwind/lostcoords_ui/engine.css +1 -0
- data/app/helpers/lostcoords/ui/badge_helper.rb +162 -0
- data/app/helpers/lostcoords/ui/button_helper.rb +70 -0
- data/app/helpers/lostcoords/ui/card_helper.rb +60 -0
- data/app/helpers/lostcoords/ui/copy_button_helper.rb +40 -0
- data/app/helpers/lostcoords/ui/dialog_helper.rb +33 -0
- data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +245 -0
- data/app/helpers/lostcoords/ui/feedback_helper.rb +30 -0
- data/app/helpers/lostcoords/ui/form_helper.rb +93 -0
- data/app/helpers/lostcoords/ui/glass_helper.rb +25 -0
- data/app/helpers/lostcoords/ui/icon_helper.rb +15 -0
- data/app/helpers/lostcoords/ui/layout_helper.rb +60 -0
- data/app/helpers/lostcoords/ui/menu_helper.rb +41 -0
- data/app/helpers/lostcoords/ui/primitives_helper.rb +152 -0
- data/app/helpers/lostcoords/ui/reading_helper.rb +15 -0
- data/app/helpers/lostcoords/ui/render_attributes_helper.rb +46 -0
- data/app/helpers/lostcoords/ui/sheet_helper.rb +54 -0
- data/app/helpers/lostcoords/ui/structures_helper.rb +159 -0
- data/app/helpers/lostcoords/ui/tailwind_helper.rb +14 -0
- data/app/helpers/lostcoords/ui/translation_helper.rb +13 -0
- data/app/helpers/lostcoords/ui/typography_helper.rb +28 -0
- data/app/javascript/lostcoords/ui/controllers/confirm_dialog_controller.js +39 -0
- data/app/javascript/lostcoords/ui/controllers/disclosure_controller.js +163 -0
- data/app/javascript/lostcoords/ui/controllers/emoji_picker_controller.js +366 -0
- data/app/javascript/lostcoords/ui/controllers/modal_controller.js +27 -0
- data/app/javascript/lostcoords/ui/controllers/row_menu_controller.js +83 -0
- data/app/javascript/lostcoords/ui/controllers/sheet_controller.js +422 -0
- data/app/javascript/lostcoords/ui/controllers/toast_controller.js +69 -0
- data/app/javascript/lostcoords/ui/controllers/ui_copy_controller.js +56 -0
- data/app/javascript/lostcoords/ui/controllers/ui_popover_controller.js +17 -0
- data/app/javascript/lostcoords/ui/controllers/ui_selection_controller.js +69 -0
- data/app/javascript/lostcoords/ui/controllers/ui_toggle_controller.js +13 -0
- data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +455 -0
- data/app/javascript/lostcoords/ui/helpers/context_dialog.js +29 -0
- data/app/javascript/lostcoords/ui/helpers/dialog.js +31 -0
- data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +155 -0
- data/app/javascript/lostcoords/ui/helpers/floating_panel.js +108 -0
- data/app/javascript/lostcoords/ui/helpers/menu_keys.js +27 -0
- data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +13 -0
- data/app/javascript/lostcoords/ui/helpers/top_layer.js +102 -0
- data/app/javascript/lostcoords/ui/helpers/viewport.js +13 -0
- data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +176 -0
- data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +29 -0
- data/app/views/lostcoords/ui/components/_form_alert.html.erb +4 -0
- data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +13 -0
- data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +5 -0
- data/app/views/lostcoords/ui/components/_skeleton.html.erb +4 -0
- data/app/views/lostcoords/ui/components/_spinner.html.erb +10 -0
- data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +8 -0
- data/config/emoji_search_aliases.yml +146 -0
- data/config/emoji_search_names.yml +1917 -0
- data/config/emoji_search_names_ja.yml +6902 -0
- data/config/helper_renames.json +113 -0
- data/config/importmap.rb +3 -0
- data/config/locales/en.yml +33 -0
- data/config/locales/es.yml +33 -0
- data/config/locales/ja.yml +33 -0
- data/lib/lostcoords/ui/action_view.rb +10 -0
- data/lib/lostcoords/ui/builders.rb +42 -0
- data/lib/lostcoords/ui/components.rb +13 -0
- data/lib/lostcoords/ui/engine.rb +22 -0
- data/lib/lostcoords/ui/helpers.rb +11 -0
- data/lib/lostcoords/ui/icons.rb +24 -0
- data/lib/lostcoords/ui/rails.rb +3 -0
- data/lib/lostcoords/ui/resources.rb +17 -0
- data/lib/lostcoords/ui/tokens.rb +14 -0
- data/lib/lostcoords/ui/version.rb +5 -0
- data/lib/lostcoords/ui.rb +2 -0
- data/lib/tasks/lostcoords_ui.rake +15 -0
- data/script/migrate_helpers.rb +136 -0
- data/vendor/heroicons/LICENSE +21 -0
- data/vendor/heroicons/icons.json +1303 -0
- metadata +203 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"avatar_class": "lc_avatar_class",
|
|
3
|
+
"avatar_stack_class": "lc_avatar_stack_class",
|
|
4
|
+
"avatar_stack_row_class": "lc_avatar_stack_row_class",
|
|
5
|
+
"badge_class": "lc_badge_class",
|
|
6
|
+
"button_class": "lc_button_class",
|
|
7
|
+
"button_variants": "lc_button_variants",
|
|
8
|
+
"card_class": "lc_card_class",
|
|
9
|
+
"card_surface_class": "lc_card_surface_class",
|
|
10
|
+
"container_class": "lc_container_class",
|
|
11
|
+
"dialog_class": "lc_dialog_class",
|
|
12
|
+
"dialog_kinds": "lc_dialog_kinds",
|
|
13
|
+
"dialog_surface_class": "lc_dialog_surface_class",
|
|
14
|
+
"dropdown_trigger_class": "lc_dropdown_trigger_class",
|
|
15
|
+
"emoji_picker_count": "lc_emoji_picker_count",
|
|
16
|
+
"emoji_picker_groups": "lc_emoji_picker_groups",
|
|
17
|
+
"emoji_picker_search_labels": "lc_emoji_picker_search_labels",
|
|
18
|
+
"emoji_picker_search_name": "lc_emoji_picker_search_name",
|
|
19
|
+
"emoji_picker_search_text": "lc_emoji_picker_search_text",
|
|
20
|
+
"emoji_picker_toneable?": "lc_emoji_picker_toneable?",
|
|
21
|
+
"emoji_picker_version": "lc_emoji_picker_version",
|
|
22
|
+
"empty_state_class": "lc_empty_state_class",
|
|
23
|
+
"eyebrow_class": "lc_eyebrow_class",
|
|
24
|
+
"field_class": "lc_field_class",
|
|
25
|
+
"field_label_class": "lc_field_label_class",
|
|
26
|
+
"form_actions_class": "lc_form_actions_class",
|
|
27
|
+
"form_alert_class": "lc_form_alert_class",
|
|
28
|
+
"form_alert_icon": "lc_form_alert_icon",
|
|
29
|
+
"form_card_class": "lc_form_card_class",
|
|
30
|
+
"glass_class": "lc_glass_class",
|
|
31
|
+
"heading_class": "lc_heading_class",
|
|
32
|
+
"heroicon": "lc_icon",
|
|
33
|
+
"icon_badge_class": "lc_icon_badge_class",
|
|
34
|
+
"icon_button_class": "lc_icon_button_class",
|
|
35
|
+
"icon_medallion_class": "lc_icon_medallion_class",
|
|
36
|
+
"inline_class": "lc_inline_class",
|
|
37
|
+
"input_class": "lc_input_class",
|
|
38
|
+
"menu_option_class": "lc_menu_option_class",
|
|
39
|
+
"menu_popover_class": "lc_menu_popover_class",
|
|
40
|
+
"pill_class": "lc_pill_class",
|
|
41
|
+
"pill_row_class": "lc_pill_row_class",
|
|
42
|
+
"popover_class": "lc_popover_class",
|
|
43
|
+
"prose_class": "lc_prose_class",
|
|
44
|
+
"section_header_class": "lc_section_header_class",
|
|
45
|
+
"segmented_class": "lc_segmented_class",
|
|
46
|
+
"segmented_tab_class": "lc_segmented_tab_class",
|
|
47
|
+
"select_class": "lc_select_class",
|
|
48
|
+
"sheet_chip_class": "lc_sheet_chip_class",
|
|
49
|
+
"sheet_data": "lc_sheet_data",
|
|
50
|
+
"sheet_datetime_input_class": "lc_sheet_datetime_input_class",
|
|
51
|
+
"sheet_row_class": "lc_sheet_row_class",
|
|
52
|
+
"sheet_row_label_class": "lc_sheet_row_label_class",
|
|
53
|
+
"sheet_text_input_class": "lc_sheet_text_input_class",
|
|
54
|
+
"skeleton_class": "lc_skeleton_class",
|
|
55
|
+
"spinner_class": "lc_spinner_class",
|
|
56
|
+
"stack_class": "lc_stack_class",
|
|
57
|
+
"status_pill_class": "lc_status_pill_class",
|
|
58
|
+
"text_class": "lc_text_class",
|
|
59
|
+
"textarea_class": "lc_textarea_class",
|
|
60
|
+
"tw": "lc_tw",
|
|
61
|
+
"ui_attributes": "lc_attributes",
|
|
62
|
+
"ui_avatar": "lc_avatar",
|
|
63
|
+
"ui_avatar_stack": "lc_avatar_stack",
|
|
64
|
+
"ui_badge": "lc_badge",
|
|
65
|
+
"ui_button": "lc_button",
|
|
66
|
+
"ui_button_attributes": "lc_button_attributes",
|
|
67
|
+
"ui_button_content": "lc_button_content",
|
|
68
|
+
"ui_button_link_to": "lc_button_link_to",
|
|
69
|
+
"ui_button_to": "lc_button_to",
|
|
70
|
+
"ui_card": "lc_card",
|
|
71
|
+
"ui_card_surface": "lc_card_surface",
|
|
72
|
+
"ui_container": "lc_container",
|
|
73
|
+
"ui_copy_button": "lc_copy_button",
|
|
74
|
+
"ui_copy_content": "lc_copy_content",
|
|
75
|
+
"ui_dialog": "lc_dialog",
|
|
76
|
+
"ui_disclosure": "lc_disclosure",
|
|
77
|
+
"ui_emoji_picker": "lc_emoji_picker",
|
|
78
|
+
"ui_empty_state": "lc_empty_state",
|
|
79
|
+
"ui_eyebrow": "lc_eyebrow",
|
|
80
|
+
"ui_field": "lc_field",
|
|
81
|
+
"ui_form_actions": "lc_form_actions",
|
|
82
|
+
"ui_form_alert": "lc_form_alert",
|
|
83
|
+
"ui_form_card": "lc_form_card",
|
|
84
|
+
"ui_heading": "lc_heading",
|
|
85
|
+
"ui_icon_badge": "lc_icon_badge",
|
|
86
|
+
"ui_icon_button": "lc_icon_button",
|
|
87
|
+
"ui_inline": "lc_inline",
|
|
88
|
+
"ui_input": "lc_input",
|
|
89
|
+
"ui_label!": "lc_label!",
|
|
90
|
+
"ui_medallion": "lc_medallion",
|
|
91
|
+
"ui_menu": "lc_menu",
|
|
92
|
+
"ui_menu_option": "lc_menu_option",
|
|
93
|
+
"ui_normalize_attributes": "lc_normalize_attributes",
|
|
94
|
+
"ui_option!": "lc_option!",
|
|
95
|
+
"ui_pill": "lc_pill",
|
|
96
|
+
"ui_pill_row": "lc_pill_row",
|
|
97
|
+
"ui_popover": "lc_popover",
|
|
98
|
+
"ui_prose": "lc_prose",
|
|
99
|
+
"ui_render_dialog": "lc_render_dialog",
|
|
100
|
+
"ui_render_panel": "lc_render_panel",
|
|
101
|
+
"ui_section": "lc_section",
|
|
102
|
+
"ui_segmented": "lc_segmented",
|
|
103
|
+
"ui_segmented_tab": "lc_segmented_tab",
|
|
104
|
+
"ui_select": "lc_select",
|
|
105
|
+
"ui_sheet": "lc_sheet",
|
|
106
|
+
"ui_skeleton": "lc_skeleton",
|
|
107
|
+
"ui_spinner": "lc_spinner",
|
|
108
|
+
"ui_stack": "lc_stack",
|
|
109
|
+
"ui_status_pill": "lc_status_pill",
|
|
110
|
+
"ui_text": "lc_text",
|
|
111
|
+
"ui_textarea": "lc_textarea",
|
|
112
|
+
"ui_toast": "lc_toast"
|
|
113
|
+
}
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Keys match the host's own helpers/ and controllers/ pins, so imports and stimulus-loading resolve the same way.
|
|
2
|
+
pin_all_from Lostcoords::Ui::Engine.root.join("app/javascript/lostcoords/ui/controllers"), under: "controllers", to: "lostcoords/ui/controllers", preload: false
|
|
3
|
+
pin_all_from Lostcoords::Ui::Engine.root.join("app/javascript/lostcoords/ui/helpers"), under: "helpers", to: "lostcoords/ui/helpers", preload: false
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
en:
|
|
2
|
+
lostcoords:
|
|
3
|
+
ui:
|
|
4
|
+
copy_button:
|
|
5
|
+
label: "Copy"
|
|
6
|
+
copied: "Copied"
|
|
7
|
+
retry: "Retry"
|
|
8
|
+
error: "Could not copy. Select the text and copy it manually."
|
|
9
|
+
emoji_picker:
|
|
10
|
+
recently_used: "Recently used"
|
|
11
|
+
search_label: "Search emoji"
|
|
12
|
+
search_placeholder: "Search emoji"
|
|
13
|
+
no_results: "No emoji found."
|
|
14
|
+
search_results: "Search results"
|
|
15
|
+
categories_nav: "Emoji categories"
|
|
16
|
+
skin_tone: "Skin tone"
|
|
17
|
+
skin_tones:
|
|
18
|
+
default: "Default"
|
|
19
|
+
light: "Light"
|
|
20
|
+
medium_light: "Medium-light"
|
|
21
|
+
medium: "Medium"
|
|
22
|
+
medium_dark: "Medium-dark"
|
|
23
|
+
dark: "Dark"
|
|
24
|
+
categories:
|
|
25
|
+
smileys_emotion: "Smileys & Emotion"
|
|
26
|
+
people_body: "People & Body"
|
|
27
|
+
animals_nature: "Animals & Nature"
|
|
28
|
+
food_drink: "Food & Drink"
|
|
29
|
+
travel_places: "Travel & Places"
|
|
30
|
+
activities: "Activities"
|
|
31
|
+
objects: "Objects"
|
|
32
|
+
symbols: "Symbols"
|
|
33
|
+
flags: "Flags"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
es:
|
|
2
|
+
lostcoords:
|
|
3
|
+
ui:
|
|
4
|
+
copy_button:
|
|
5
|
+
label: "Copiar"
|
|
6
|
+
copied: "Copiado"
|
|
7
|
+
retry: "Reintentar"
|
|
8
|
+
error: "No se pudo copiar. Selecciona el texto y cópialo manualmente."
|
|
9
|
+
emoji_picker:
|
|
10
|
+
recently_used: "Usados recientemente"
|
|
11
|
+
search_label: "Buscar emoji"
|
|
12
|
+
search_placeholder: "Buscar emoji"
|
|
13
|
+
no_results: "No se encontraron emojis."
|
|
14
|
+
search_results: "Resultados"
|
|
15
|
+
categories_nav: "Categorías de emojis"
|
|
16
|
+
skin_tone: "Tono de piel"
|
|
17
|
+
skin_tones:
|
|
18
|
+
default: "Predeterminado"
|
|
19
|
+
light: "Claro"
|
|
20
|
+
medium_light: "Medio claro"
|
|
21
|
+
medium: "Medio"
|
|
22
|
+
medium_dark: "Medio oscuro"
|
|
23
|
+
dark: "Oscuro"
|
|
24
|
+
categories:
|
|
25
|
+
smileys_emotion: "Caras y emociones"
|
|
26
|
+
people_body: "Personas y cuerpo"
|
|
27
|
+
animals_nature: "Animales y naturaleza"
|
|
28
|
+
food_drink: "Comida y bebida"
|
|
29
|
+
travel_places: "Viajes y lugares"
|
|
30
|
+
activities: "Actividades"
|
|
31
|
+
objects: "Objetos"
|
|
32
|
+
symbols: "Símbolos"
|
|
33
|
+
flags: "Banderas"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
ja:
|
|
2
|
+
lostcoords:
|
|
3
|
+
ui:
|
|
4
|
+
copy_button:
|
|
5
|
+
label: "コピー"
|
|
6
|
+
copied: "コピー済み"
|
|
7
|
+
retry: "再試行"
|
|
8
|
+
error: "コピーできませんでした。テキストを選択して手動でコピーしてください。"
|
|
9
|
+
emoji_picker:
|
|
10
|
+
recently_used: "最近使った絵文字"
|
|
11
|
+
search_label: "絵文字を検索"
|
|
12
|
+
search_placeholder: "絵文字を検索"
|
|
13
|
+
no_results: "一致する絵文字がありません。"
|
|
14
|
+
search_results: "検索結果"
|
|
15
|
+
categories_nav: "絵文字のカテゴリ"
|
|
16
|
+
skin_tone: "肌の色"
|
|
17
|
+
skin_tones:
|
|
18
|
+
default: "標準"
|
|
19
|
+
light: "薄い"
|
|
20
|
+
medium_light: "やや薄い"
|
|
21
|
+
medium: "中間"
|
|
22
|
+
medium_dark: "やや濃い"
|
|
23
|
+
dark: "濃い"
|
|
24
|
+
categories:
|
|
25
|
+
smileys_emotion: "表情と感情"
|
|
26
|
+
people_body: "人と体"
|
|
27
|
+
animals_nature: "動物と自然"
|
|
28
|
+
food_drink: "食べ物と飲み物"
|
|
29
|
+
travel_places: "旅行と場所"
|
|
30
|
+
activities: "アクティビティ"
|
|
31
|
+
objects: "もの"
|
|
32
|
+
symbols: "記号"
|
|
33
|
+
flags: "旗"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require "bigdecimal"
|
|
2
|
+
require "action_view"
|
|
3
|
+
require "active_support/core_ext/hash"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "tailwind_merge"
|
|
6
|
+
require "lostcoords/ui/resources"
|
|
7
|
+
require "lostcoords/ui/icons"
|
|
8
|
+
require "lostcoords/ui/helpers"
|
|
9
|
+
|
|
10
|
+
Lostcoords::Ui.load_translations!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "prism"
|
|
2
|
+
require_relative "version"
|
|
3
|
+
|
|
4
|
+
module Lostcoords::Ui
|
|
5
|
+
module Builders
|
|
6
|
+
HELPERS = File.expand_path("../../../app/helpers/lostcoords/ui", __dir__)
|
|
7
|
+
|
|
8
|
+
Builder = Data.define(:helper, :name, :parameters, :path, :line) do
|
|
9
|
+
def signature = parameters ? "#{name}(#{parameters})" : name.to_s
|
|
10
|
+
|
|
11
|
+
def component?
|
|
12
|
+
!name.to_s.end_with?("_class") &&
|
|
13
|
+
!%i[lc_tw lc_sheet_data lc_form_alert_icon].include?(name) &&
|
|
14
|
+
helper != "EmojiPickerHelper"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.all
|
|
19
|
+
definitions.reject(&:component?)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.definitions
|
|
23
|
+
Dir["#{HELPERS}/*_helper.rb"].sort.flat_map { |path| public_instance_methods_in(path) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.markdown
|
|
27
|
+
all.map { |builder| "- `#{builder.signature}` (#{builder.helper})" }.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.public_instance_methods_in(path)
|
|
31
|
+
namespace = Prism.parse_file(path).value.statements.body.grep(Prism::ModuleNode).first
|
|
32
|
+
helper = namespace.body.body.first
|
|
33
|
+
visible = true
|
|
34
|
+
helper.body.body.filter_map do |node|
|
|
35
|
+
visible = false if node.is_a?(Prism::CallNode) && node.name == :private && node.arguments.nil?
|
|
36
|
+
next unless visible && node.is_a?(Prism::DefNode) && node.receiver.nil?
|
|
37
|
+
|
|
38
|
+
Builder.new(helper.constant_path.slice, node.name, node.parameters&.slice, path, node.location.start_line)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Lostcoords::Ui
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace Lostcoords::Ui
|
|
4
|
+
|
|
5
|
+
initializer "lostcoords.ui.assets" do |app|
|
|
6
|
+
next unless app.config.respond_to?(:assets)
|
|
7
|
+
|
|
8
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
initializer "lostcoords.ui.importmap", before: "importmap" do |app|
|
|
12
|
+
next unless app.config.respond_to?(:importmap)
|
|
13
|
+
|
|
14
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
15
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "lostcoords.ui.helpers" do
|
|
19
|
+
ActiveSupport.on_load(:action_view) { include Lostcoords::Ui::Helpers }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Lostcoords::Ui
|
|
2
|
+
module Helpers
|
|
3
|
+
paths = Dir[Lostcoords::Ui.root.join("app/helpers/lostcoords/ui/*_helper.rb")].sort_by do |path|
|
|
4
|
+
[ %w[tailwind_helper.rb button_helper.rb].index(File.basename(path)) || 2, path ]
|
|
5
|
+
end
|
|
6
|
+
paths.each do |path|
|
|
7
|
+
require path
|
|
8
|
+
include Lostcoords::Ui.const_get(File.basename(path, ".rb").camelize)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "nokogiri"
|
|
3
|
+
|
|
4
|
+
module Lostcoords::Ui
|
|
5
|
+
module Icons
|
|
6
|
+
TEMPLATES = {}
|
|
7
|
+
LOCK = Mutex.new
|
|
8
|
+
|
|
9
|
+
def self.fetch(name, variant)
|
|
10
|
+
LOCK.synchronize do
|
|
11
|
+
@catalog ||= JSON.parse(File.read(Lostcoords::Ui.root.join("vendor/heroicons/icons.json")), freeze: true).fetch("icons")
|
|
12
|
+
source = @catalog.fetch(variant.to_s).fetch(name.to_s)
|
|
13
|
+
template = TEMPLATES[source] ||= begin
|
|
14
|
+
document = Nokogiri::XML(source) { |config| config.strict.nonet }
|
|
15
|
+
document.xpath("//text()[normalize-space(.) = '']").remove
|
|
16
|
+
document
|
|
17
|
+
end
|
|
18
|
+
template.dup.root
|
|
19
|
+
end
|
|
20
|
+
rescue KeyError
|
|
21
|
+
raise ArgumentError, "Unknown Heroicon #{name.inspect} with variant #{variant.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require_relative "version"
|
|
3
|
+
|
|
4
|
+
module Lostcoords::Ui
|
|
5
|
+
def self.root
|
|
6
|
+
@root ||= Pathname.new(File.expand_path("../../..", __dir__)).freeze
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.view_paths
|
|
10
|
+
[ root.join("app/views").to_s ]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.load_translations!
|
|
14
|
+
paths = (Dir[root.join("config/locales/*.yml")] + I18n.load_path).uniq
|
|
15
|
+
I18n.load_path = paths unless I18n.load_path == paths
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative "version"
|
|
2
|
+
|
|
3
|
+
module Lostcoords::Ui
|
|
4
|
+
module Tokens
|
|
5
|
+
THEME_PATH = File.expand_path("../../../app/assets/tailwind/lostcoords/ui/theme.css", __dir__)
|
|
6
|
+
DECLARATION = /(?<![\w-])(--[\w-]+)\s*:/
|
|
7
|
+
|
|
8
|
+
def self.names
|
|
9
|
+
File.read(THEME_PATH).scan(DECLARATION).flatten.uniq.sort
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
TOKENS = Tokens.names.freeze
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
namespace :lostcoords do
|
|
2
|
+
namespace :ui do
|
|
3
|
+
desc "Print the component rendering helpers and their signatures"
|
|
4
|
+
task :components do
|
|
5
|
+
require "lostcoords/ui/components"
|
|
6
|
+
puts Lostcoords::Ui::Components.markdown
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "Print the style builders and their signatures"
|
|
10
|
+
task :builders do
|
|
11
|
+
require "lostcoords/ui/builders"
|
|
12
|
+
puts Lostcoords::Ui::Builders.markdown
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "find"
|
|
5
|
+
require "optparse"
|
|
6
|
+
require "ripper"
|
|
7
|
+
|
|
8
|
+
module HelperMigration
|
|
9
|
+
LIBRARY = File.expand_path("..", __dir__)
|
|
10
|
+
NAMES = JSON.parse(File.read(File.join(LIBRARY, "config/helper_renames.json"))).freeze
|
|
11
|
+
PARTIALS = %w[dialog disclosure].to_h do |name|
|
|
12
|
+
[ "lostcoords/ui/components/ui_#{name}", "lostcoords/ui/components/lc_#{name}" ]
|
|
13
|
+
end.freeze
|
|
14
|
+
|
|
15
|
+
def self.method_locations(node, locations = {})
|
|
16
|
+
return locations unless node.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
name = case node.first
|
|
19
|
+
when :def, :vcall, :fcall, :command, :symbol then node[1]
|
|
20
|
+
when :defs, :call, :command_call then node[3]
|
|
21
|
+
end
|
|
22
|
+
locations[name[2]] = true if name.is_a?(Array) && name.first == :@ident
|
|
23
|
+
node.each { |child| method_locations(child, locations) }
|
|
24
|
+
locations
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.rewrite(source, program: source, prefix_lines: 0)
|
|
28
|
+
syntax = Ripper.sexp(program)
|
|
29
|
+
raise ArgumentError, "Cannot parse Ruby source for helper migration" unless syntax
|
|
30
|
+
|
|
31
|
+
methods = method_locations(syntax)
|
|
32
|
+
offsets = [ 0 ]
|
|
33
|
+
source.each_line { |line| offsets << offsets.last + line.bytesize }
|
|
34
|
+
contexts = []
|
|
35
|
+
edits = Ripper.lex(program).filter_map do |position, type, value, _|
|
|
36
|
+
replacement = case type
|
|
37
|
+
when :on_ident then NAMES[value] if methods[position]
|
|
38
|
+
when :on_tstring_content then contexts.last == :symbol ? NAMES[value] : PARTIALS[value]
|
|
39
|
+
end
|
|
40
|
+
case type
|
|
41
|
+
when :on_symbeg then contexts << :symbol unless value == ":"
|
|
42
|
+
when :on_qsymbols_beg, :on_symbols_beg then contexts << :symbol
|
|
43
|
+
when :on_tstring_beg, :on_heredoc_beg, :on_qwords_beg, :on_words_beg, :on_regexp_beg then contexts << :string
|
|
44
|
+
when :on_tstring_end, :on_heredoc_end, :on_regexp_end then contexts.pop
|
|
45
|
+
end
|
|
46
|
+
line = position[0] - prefix_lines
|
|
47
|
+
[ offsets.fetch(line - 1) + position[1], value.bytesize, replacement ] if replacement && line.positive?
|
|
48
|
+
end
|
|
49
|
+
updated = source.b
|
|
50
|
+
edits.reverse_each { |offset, length, value| updated[offset, length] = value.b }
|
|
51
|
+
updated.force_encoding(source.encoding)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.ruby(source)
|
|
55
|
+
rewrite(source)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.erb(source)
|
|
59
|
+
program = source.b.gsub(/[^\r\n]/n, " ")
|
|
60
|
+
source.to_enum(:scan, /(<%(?!%)(?:==?|-)?)(.*?)(-?%>)/m).each do
|
|
61
|
+
match = Regexp.last_match
|
|
62
|
+
opening, body, closing = match.captures
|
|
63
|
+
next if body.start_with?("#")
|
|
64
|
+
|
|
65
|
+
start = match.pre_match.bytesize + opening.bytesize
|
|
66
|
+
program[start, body.bytesize] = body.b
|
|
67
|
+
program[start + body.bytesize, closing.bytesize] = ";" + " " * (closing.bytesize - 1)
|
|
68
|
+
end
|
|
69
|
+
signature = source[/<%#\s*locals:\s*\((.*?)\)\s*%>/m, 1]
|
|
70
|
+
prefix = "def __template(#{signature})\n"
|
|
71
|
+
suffix = "\nend"
|
|
72
|
+
rewrite(source, program: prefix + program.force_encoding(source.encoding) + suffix, prefix_lines: prefix.count("\n"))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.markdown(source)
|
|
76
|
+
source = source.gsub(/(^```(ruby|rb|erb)[^\n]*\n)(.*?)(^```[ \t]*$)/m) do
|
|
77
|
+
opening, language, body, closing = Regexp.last_match.captures
|
|
78
|
+
"#{opening}#{language == 'erb' ? erb(body) : ruby(body)}#{closing}"
|
|
79
|
+
end
|
|
80
|
+
source.gsub(/`([^`\n]+)`/) { |match| NAMES.key?(Regexp.last_match[1]) ? "`#{NAMES.fetch(Regexp.last_match[1])}`" : match }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.changes(root)
|
|
84
|
+
files = Find.find(root).filter_map do |path|
|
|
85
|
+
next if File.symlink?(path)
|
|
86
|
+
if File.directory?(path)
|
|
87
|
+
name = File.basename(path)
|
|
88
|
+
Find.prune if path != root && (name.start_with?(".") || %w[node_modules vendor tmp log storage pkg coverage builds dist].include?(name))
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
path.delete_prefix("#{root}/")
|
|
92
|
+
end
|
|
93
|
+
files.sort.filter_map do |relative|
|
|
94
|
+
path = File.join(root, relative)
|
|
95
|
+
next if File.symlink?(path) || relative.start_with?("vendor/")
|
|
96
|
+
next if root == LIBRARY && (relative == "README.md" || relative.start_with?("docs/", "plans/"))
|
|
97
|
+
|
|
98
|
+
extension = File.extname(path)
|
|
99
|
+
next unless %w[.rb .erb .md].include?(extension) || extension.empty?
|
|
100
|
+
|
|
101
|
+
source = File.binread(path).force_encoding(Encoding::UTF_8)
|
|
102
|
+
updated = case extension
|
|
103
|
+
when ".rb" then ruby(source)
|
|
104
|
+
when ".erb" then erb(source)
|
|
105
|
+
when ".md" then markdown(source)
|
|
106
|
+
else ruby(source) if source.lines.first&.match?(/\A#!.*ruby/)
|
|
107
|
+
end
|
|
108
|
+
next unless updated
|
|
109
|
+
|
|
110
|
+
name = File.basename(path).sub(/\A_ui_(dialog|disclosure)\.html\.erb\z/, '_lc_\1.html.erb')
|
|
111
|
+
destination = File.join(File.dirname(path), name)
|
|
112
|
+
abort "Refusing to overwrite #{destination}" if destination != path && (File.exist?(destination) || File.symlink?(destination))
|
|
113
|
+
[ path, destination, updated ] if updated != source || destination != path
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if $PROGRAM_NAME == __FILE__
|
|
119
|
+
write = false
|
|
120
|
+
OptionParser.new do |options|
|
|
121
|
+
options.banner = "Usage: ruby migrate_helpers.rb [checkout] [--write]"
|
|
122
|
+
options.on("--write", "Apply the migration after checking every destination") { write = true }
|
|
123
|
+
end.parse!
|
|
124
|
+
abort "Expected one checkout path" if ARGV.size > 1
|
|
125
|
+
root = File.realpath(ARGV.first || Dir.pwd)
|
|
126
|
+
changes = HelperMigration.changes(root)
|
|
127
|
+
changes.each do |path, destination, source|
|
|
128
|
+
puts path.delete_prefix("#{root}/")
|
|
129
|
+
next unless write
|
|
130
|
+
|
|
131
|
+
File.binwrite(path, source)
|
|
132
|
+
File.rename(path, destination) unless path == destination
|
|
133
|
+
end
|
|
134
|
+
puts "#{changes.size} files #{write ? 'updated' : 'would change'}"
|
|
135
|
+
exit 1 if changes.any? && !write
|
|
136
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Tailwind Labs, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|