lostcoords-ui 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +303 -89
- data/app/assets/tailwind/lostcoords/ui/avatar.css +6 -6
- data/app/assets/tailwind/lostcoords/ui/base.css +45 -0
- data/app/assets/tailwind/lostcoords/ui/card.css +20 -22
- data/app/assets/tailwind/lostcoords/ui/choice.css +39 -0
- data/app/assets/tailwind/lostcoords/ui/code-block.css +10 -0
- data/app/assets/tailwind/lostcoords/ui/components.css +25 -0
- data/app/assets/tailwind/lostcoords/ui/copy-button.css +9 -9
- data/app/assets/tailwind/lostcoords/ui/dialog.css +17 -17
- data/app/assets/tailwind/lostcoords/ui/disclosure.css +5 -5
- data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +43 -43
- data/app/assets/tailwind/lostcoords/ui/field.css +2 -2
- data/app/assets/tailwind/lostcoords/ui/glass.css +9 -8
- data/app/assets/tailwind/lostcoords/ui/layout.css +2 -2
- data/app/assets/tailwind/lostcoords/ui/popover.css +14 -14
- data/app/assets/tailwind/lostcoords/ui/reading.css +24 -24
- data/app/assets/tailwind/lostcoords/ui/sheet.css +60 -60
- data/app/assets/tailwind/lostcoords/ui/squircle.css +7 -27
- data/app/assets/tailwind/lostcoords/ui/table.css +51 -0
- data/app/assets/tailwind/lostcoords/ui/theme.css +194 -225
- data/app/assets/tailwind/lostcoords/ui/tooltip.css +50 -0
- data/app/assets/tailwind/lostcoords/ui/typography.css +37 -61
- data/app/assets/tailwind/lostcoords/ui/ui.css +2 -21
- data/app/assets/tailwind/lostcoords/ui/utilities.css +14 -22
- data/app/helpers/lostcoords/ui/badge_helper.rb +67 -110
- data/app/helpers/lostcoords/ui/button_helper.rb +36 -33
- data/app/helpers/lostcoords/ui/card_helper.rb +9 -52
- data/app/helpers/lostcoords/ui/choice_helper.rb +83 -0
- data/app/helpers/lostcoords/ui/code_block_helper.rb +22 -0
- data/app/helpers/lostcoords/ui/copy_button_helper.rb +7 -7
- data/app/helpers/lostcoords/ui/description_list_helper.rb +12 -0
- data/app/helpers/lostcoords/ui/dialog_helper.rb +6 -13
- data/app/helpers/lostcoords/ui/feedback_helper.rb +25 -6
- data/app/helpers/lostcoords/ui/form_helper.rb +36 -59
- data/app/helpers/lostcoords/ui/glass_helper.rb +7 -7
- data/app/helpers/lostcoords/ui/layout_helper.rb +13 -11
- data/app/helpers/lostcoords/ui/menu_helper.rb +8 -8
- data/app/helpers/lostcoords/ui/navigation_helper.rb +131 -0
- data/app/helpers/lostcoords/ui/overlay_helper.rb +17 -0
- data/app/helpers/lostcoords/ui/primitives_helper.rb +39 -61
- data/app/helpers/lostcoords/ui/reading_helper.rb +2 -2
- data/app/helpers/lostcoords/ui/sheet_helper.rb +3 -45
- data/app/helpers/lostcoords/ui/structures_helper.rb +55 -64
- data/app/helpers/lostcoords/ui/table_helper.rb +11 -0
- data/app/helpers/lostcoords/ui/tailwind_helper.rb +13 -4
- data/app/helpers/lostcoords/ui/typography_helper.rb +11 -11
- data/app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js +23 -0
- data/app/javascript/lostcoords/ui/controllers/{disclosure_controller.js → lc_collapsible_controller.js} +3 -3
- data/app/javascript/lostcoords/ui/controllers/{confirm_dialog_controller.js → lc_confirm_dialog_controller.js} +4 -4
- data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js} +1 -1
- data/app/javascript/lostcoords/ui/controllers/{emoji_picker_controller.js → lc_emoji_picker_controller.js} +4 -4
- data/app/javascript/lostcoords/ui/controllers/{row_menu_controller.js → lc_menu_controller.js} +3 -3
- data/app/javascript/lostcoords/ui/controllers/{ui_popover_controller.js → lc_popover_controller.js} +1 -1
- data/app/javascript/lostcoords/ui/controllers/{ui_selection_controller.js → lc_selection_controller.js} +1 -1
- data/app/javascript/lostcoords/ui/controllers/{sheet_controller.js → lc_sheet_controller.js} +15 -15
- data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +39 -0
- data/app/javascript/lostcoords/ui/controllers/{toast_controller.js → lc_toast_controller.js} +2 -2
- data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +14 -0
- data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +5 -5
- data/app/javascript/lostcoords/ui/helpers/context_dialog.js +6 -6
- data/app/javascript/lostcoords/ui/helpers/dialog.js +2 -2
- data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +7 -7
- data/app/javascript/lostcoords/ui/helpers/floating_panel.js +2 -2
- data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +4 -4
- data/app/javascript/lostcoords/ui/index.js +51 -0
- data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +30 -30
- data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +18 -0
- data/app/views/lostcoords/ui/components/_lc_collapsible.html.erb +5 -0
- data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +11 -8
- data/app/views/lostcoords/ui/components/_lc_floating_panel.html.erb +8 -0
- data/config/helper_renames.json +23 -25
- data/config/importmap.rb +10 -2
- data/config/locales/en.yml +23 -0
- data/config/locales/es.yml +23 -0
- data/config/locales/ja.yml +23 -0
- data/lib/lostcoords/ui/action_view.rb +5 -3
- data/lib/lostcoords/ui/builders.rb +1 -1
- data/lib/lostcoords/ui/form_builder.rb +113 -0
- data/lib/lostcoords/ui/parts.rb +154 -0
- data/lib/lostcoords/ui/tokens.rb +24 -1
- data/lib/lostcoords/ui/version.rb +1 -1
- metadata +33 -17
- data/app/views/lostcoords/ui/components/_form_alert.html.erb +0 -4
- data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +0 -5
- data/app/views/lostcoords/ui/components/_skeleton.html.erb +0 -4
- data/app/views/lostcoords/ui/components/_spinner.html.erb +0 -10
- data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +0 -8
- /data/app/javascript/lostcoords/ui/controllers/{ui_copy_controller.js → lc_copy_controller.js} +0 -0
- /data/app/javascript/lostcoords/ui/controllers/{ui_toggle_controller.js → lc_toggle_controller.js} +0 -0
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module PrimitivesHelper
|
|
3
|
-
|
|
4
|
-
primary: :accent,
|
|
5
|
-
secondary: :secondary,
|
|
6
|
-
ghost: :ghost,
|
|
7
|
-
destructive: :danger
|
|
8
|
-
}.freeze
|
|
9
|
-
|
|
10
|
-
def lc_button(content = nil, variant: :primary, size: :base, icon: nil, icon_position: :start, **attributes, &block)
|
|
3
|
+
def lc_button(content = nil, variant: :primary, size: :md, icon: nil, icon_position: :start, **attributes, &block)
|
|
11
4
|
tag.button(lc_button_content(content, icon, icon_position, &block), **lc_button_attributes(variant, size, { type: "button" }, attributes))
|
|
12
5
|
end
|
|
13
6
|
|
|
14
|
-
def lc_button_link_to(content = nil, url = nil, variant: :primary, size: :
|
|
7
|
+
def lc_button_link_to(content = nil, url = nil, variant: :primary, size: :md, icon: nil, icon_position: :start, **attributes, &block)
|
|
15
8
|
attributes = lc_button_attributes(variant, size, {}, attributes)
|
|
16
9
|
raise ArgumentError, "Links cannot be disabled; use lc_button for a disabled control" if attributes[:disabled]
|
|
17
10
|
|
|
@@ -23,7 +16,7 @@ module Lostcoords::Ui
|
|
|
23
16
|
link_to(lc_button_content(content, icon, icon_position), url, attributes)
|
|
24
17
|
end
|
|
25
18
|
|
|
26
|
-
def lc_button_to(content = nil, url = nil, variant: :primary, size: :
|
|
19
|
+
def lc_button_to(content = nil, url = nil, variant: :primary, size: :md, icon: nil, icon_position: :start, **attributes, &block)
|
|
27
20
|
if block
|
|
28
21
|
url ||= content
|
|
29
22
|
content = capture(&block)
|
|
@@ -32,10 +25,10 @@ module Lostcoords::Ui
|
|
|
32
25
|
button_to(lc_button_content(content, icon, icon_position), url, lc_button_attributes(variant, size, {}, attributes))
|
|
33
26
|
end
|
|
34
27
|
|
|
35
|
-
def lc_icon_button(label, icon:, size: :
|
|
28
|
+
def lc_icon_button(label, icon:, size: :md, shape: :circle, **attributes)
|
|
36
29
|
size = lc_option!(:size, size, ButtonHelper::ICON_BUTTON_SIZES.keys)
|
|
37
30
|
shape = lc_option!(:shape, shape, ButtonHelper::ICON_BUTTON_SHAPES.keys)
|
|
38
|
-
defaults = { type: "button", class: lc_icon_button_class(size: size, shape: shape), aria: { label: label } }
|
|
31
|
+
defaults = { type: "button", class: lc_icon_button_class(size: size, shape: shape), aria: { label: label }, data: { slot: "icon-button", size: size } }
|
|
39
32
|
attributes = lc_attributes(defaults, attributes)
|
|
40
33
|
accessible_label = attributes.fetch(:"aria-label", attributes.dig(:aria, :label))
|
|
41
34
|
raise ArgumentError, "lc_icon_button requires a nonblank accessible label" if label.blank? || accessible_label.blank?
|
|
@@ -47,36 +40,18 @@ module Lostcoords::Ui
|
|
|
47
40
|
|
|
48
41
|
def lc_badge(content = nil, tone: :neutral, **attributes, &block)
|
|
49
42
|
tone = lc_option!(:tone, tone, BadgeHelper::BADGE_TONES.keys)
|
|
50
|
-
tag.span(content, **lc_attributes({ class: lc_badge_class(tone: tone) }, attributes), &block)
|
|
43
|
+
tag.span(content, **lc_attributes({ class: lc_badge_class(tone: tone), data: { slot: "badge", tone: tone } }, attributes), &block)
|
|
51
44
|
end
|
|
52
45
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
tag.span(content, **lc_attributes({ class: lc_status_pill_class(tone: tone) }, attributes), &block)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def lc_eyebrow(content = nil, **attributes, &block)
|
|
59
|
-
tag.span(content, **lc_attributes({ class: lc_eyebrow_class }, attributes), &block)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def lc_icon_badge(icon:, size: :base, **attributes)
|
|
63
|
-
size = lc_option!(:size, size, BadgeHelper::ICON_BADGE_SIZES.keys)
|
|
64
|
-
tag.span(**lc_attributes({ class: lc_icon_badge_class(size: size) }, attributes)) do
|
|
46
|
+
def lc_icon_badge(icon:, size: :md, shape: :square, tone: :neutral, **attributes)
|
|
47
|
+
tag.span(**lc_attributes({ class: lc_icon_badge_class(size: size, shape: shape, tone: tone), data: { slot: "icon-badge" } }, attributes)) do
|
|
65
48
|
lc_icon(icon, options: { class: "size-[1em]", "aria-hidden": true })
|
|
66
49
|
end
|
|
67
50
|
end
|
|
68
51
|
|
|
69
|
-
def
|
|
70
|
-
size = lc_option!(:size, size, BadgeHelper::ICON_MEDALLION_SIZES.keys)
|
|
71
|
-
tone = lc_option!(:tone, tone, BadgeHelper::ICON_MEDALLION_TONES.keys)
|
|
72
|
-
tag.span(**lc_attributes({ class: lc_icon_medallion_class(size: size, tone: tone) }, attributes)) do
|
|
73
|
-
lc_icon(icon, options: { class: "size-1/2", "aria-hidden": true })
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def lc_avatar(content = nil, src: nil, alt: nil, size: :base, ink: false, linked: false, **attributes, &block)
|
|
52
|
+
def lc_avatar(content = nil, src: nil, alt: nil, size: :md, ink: false, linked: false, **attributes, &block)
|
|
78
53
|
size = lc_option!(:size, size, BadgeHelper::AVATAR_SIZES.keys)
|
|
79
|
-
defaults = { class: lc_avatar_class(size: size, ink: ink, linked: linked) }
|
|
54
|
+
defaults = { class: lc_avatar_class(size: size, ink: ink, linked: linked), data: { slot: "avatar" } }
|
|
80
55
|
if src
|
|
81
56
|
tag.img(**lc_attributes(defaults.merge(src: src, alt: alt.to_s, class: lc_tw(defaults[:class], "object-cover")), attributes))
|
|
82
57
|
else
|
|
@@ -85,51 +60,55 @@ module Lostcoords::Ui
|
|
|
85
60
|
end
|
|
86
61
|
|
|
87
62
|
def lc_avatar_stack(compact: false, **attributes, &block)
|
|
88
|
-
tag.span(**lc_attributes({ class: lc_avatar_stack_class(compact: compact) }, attributes)) do
|
|
63
|
+
tag.span(**lc_attributes({ class: lc_avatar_stack_class(compact: compact), data: { slot: "avatar-stack" } }, attributes)) do
|
|
89
64
|
tag.span(class: lc_avatar_stack_row_class, &block)
|
|
90
65
|
end
|
|
91
66
|
end
|
|
92
67
|
|
|
93
|
-
|
|
94
|
-
|
|
68
|
+
# Yields a card builder with header, title, description, content and footer parts. The card stacks
|
|
69
|
+
# its children with the same inset it pads with.
|
|
70
|
+
def lc_card(href: nil, interactive: false, padding: :md, **attributes, &block)
|
|
71
|
+
defaults = { class: lc_card_class(interactive: interactive || !href.nil?, padding: padding, extra: "flex flex-col gap-inset"), data: { slot: "card" } }
|
|
72
|
+
content = capture(Parts::Card.new(self), &block) if block
|
|
95
73
|
if href.nil?
|
|
96
|
-
tag.div(**lc_attributes(defaults, attributes)
|
|
74
|
+
tag.div(content, **lc_attributes(defaults, attributes))
|
|
97
75
|
else
|
|
98
|
-
defaults.merge!(href: href, class: lc_tw(defaults[:class], "
|
|
99
|
-
tag.a(**lc_attributes(defaults, attributes)
|
|
76
|
+
defaults.merge!(href: href, class: lc_tw(defaults[:class], "no-underline text-inherit"))
|
|
77
|
+
tag.a(content, **lc_attributes(defaults, attributes))
|
|
100
78
|
end
|
|
101
79
|
end
|
|
102
80
|
|
|
103
|
-
def lc_card_surface(**attributes, &block)
|
|
104
|
-
tag.div(**lc_attributes({ class: lc_card_surface_class }, attributes), &block)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def lc_form_card(padding: :default, **attributes, &block)
|
|
108
|
-
padding = lc_option!(:padding, padding, CardHelper::FORM_CARD_PADDINGS.keys)
|
|
109
|
-
tag.div(**lc_attributes({ class: lc_form_card_class(padding: padding) }, attributes), &block)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
81
|
def lc_empty_state(**attributes, &block)
|
|
113
|
-
tag.div(**lc_attributes({ class: lc_empty_state_class }, attributes), &block)
|
|
82
|
+
tag.div(**lc_attributes({ class: lc_empty_state_class, data: { slot: "empty-state" } }, attributes), &block)
|
|
114
83
|
end
|
|
115
84
|
|
|
116
|
-
def lc_spinner(size: :
|
|
85
|
+
def lc_spinner(size: :md, label: lc_t("lostcoords.ui.spinner.label"), **attributes)
|
|
117
86
|
size = lc_option!(:size, size, FeedbackHelper::SPINNER_SIZES.keys)
|
|
118
|
-
defaults = { class: lc_spinner_class(size: size), role: ("status" if label.present?), aria: { hidden: (true if label.blank?) } }
|
|
87
|
+
defaults = { class: lc_spinner_class(size: size), role: ("status" if label.present?), aria: { hidden: (true if label.blank?) }, data: { slot: "spinner" } }
|
|
119
88
|
tag.span(**lc_attributes(defaults, attributes)) do
|
|
120
89
|
tag.span(label, class: "sr-only") if label.present?
|
|
121
90
|
end
|
|
122
91
|
end
|
|
123
92
|
|
|
124
93
|
def lc_skeleton(**attributes)
|
|
125
|
-
tag.span(**lc_attributes({ class: lc_tw(lc_skeleton_class, "h-4 w-full"), aria: { hidden: true } }, attributes))
|
|
94
|
+
tag.span(**lc_attributes({ class: lc_tw(lc_skeleton_class, "h-4 w-full"), aria: { hidden: true }, data: { slot: "skeleton" } }, attributes))
|
|
126
95
|
end
|
|
127
96
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
97
|
+
# A block adds content below the message and yields a builder with an actions part.
|
|
98
|
+
def lc_alert(message = nil, title: nil, tone: :destructive, **attributes, &block)
|
|
99
|
+
tone = lc_option!(:tone, tone, FeedbackHelper::ALERT_ICONS.keys)
|
|
100
|
+
raise ArgumentError, "lc_alert needs a message, a title or a block" if message.blank? && title.blank? && block.nil?
|
|
101
|
+
|
|
102
|
+
defaults = { class: lc_alert_class, role: (tone == :destructive ? "alert" : "status"), data: { tone: tone, slot: "alert" } }
|
|
103
|
+
extra = capture(Parts::Alert.new(self), &block) if block
|
|
104
|
+
text = "m-0 min-w-0 text-pretty [overflow-wrap:anywhere]"
|
|
131
105
|
tag.div(**lc_attributes(defaults, attributes)) do
|
|
132
|
-
|
|
106
|
+
body = [
|
|
107
|
+
(tag.p(title, class: "#{text} font-semibold", data: { slot: "alert-title" }) if title.present?),
|
|
108
|
+
(tag.p(message, class: lc_tw(text, ("font-normal" if title.present?)), data: { slot: "alert-description" }) if message.present?),
|
|
109
|
+
extra.presence
|
|
110
|
+
].compact
|
|
111
|
+
safe_join([ lc_alert_icon(tone: tone), tag.div(safe_join(body), class: "flex min-w-0 flex-col gap-tight") ])
|
|
133
112
|
end
|
|
134
113
|
end
|
|
135
114
|
|
|
@@ -144,9 +123,8 @@ module Lostcoords::Ui
|
|
|
144
123
|
end
|
|
145
124
|
|
|
146
125
|
def lc_button_attributes(variant, size, defaults, attributes)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
lc_attributes(defaults.merge(class: lc_button_class(variant: BUTTON_STYLES.fetch(variant), size: size)), attributes)
|
|
126
|
+
data = { slot: "button", variant: lc_option!(:variant, variant, ButtonHelper::BUTTON_VARIANTS.keys), size: lc_option!(:size, size, ButtonHelper::BUTTON_SIZES.keys) }
|
|
127
|
+
lc_attributes(defaults.merge(class: lc_button_class(variant: variant, size: size), data: data), attributes)
|
|
150
128
|
end
|
|
151
129
|
end
|
|
152
130
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module ReadingHelper
|
|
3
3
|
PROSE_BASE =
|
|
4
|
-
"prose
|
|
5
|
-
"text-(length:--
|
|
4
|
+
"prose lc-prose min-w-0 max-w-(--lc-reading-measure,65ch) " \
|
|
5
|
+
"text-(length:--lc-reading-size,1rem) leading-(--lc-reading-leading,1.75)"
|
|
6
6
|
|
|
7
7
|
def lc_prose_class(extra: nil)
|
|
8
8
|
lc_tw(PROSE_BASE, extra)
|
|
@@ -1,54 +1,12 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module SheetHelper
|
|
3
|
-
|
|
4
|
-
"grid grid-cols-[1.25rem_minmax(0,1fr)] @md:grid-cols-[8rem_minmax(0,1fr)] items-center " \
|
|
5
|
-
"gap-x-3 gap-y-2 min-h-14 px-4 py-2.5 bg-paper-2"
|
|
6
|
-
|
|
7
|
-
SHEET_ROW_LABEL_BASE =
|
|
8
|
-
"flex items-center gap-2 font-body text-sm font-semibold text-ink-3 " \
|
|
9
|
-
"[&_svg]:size-5 @md:[&_svg]:size-4.5 [&_svg]:shrink-0 @max-md:[&>span]:sr-only"
|
|
10
|
-
|
|
11
|
-
SHEET_CHIP_BASE = "max-w-full min-w-0 h-11 min-h-11 py-0 px-3.5 gap-2 text-sm bg-paper border-0 aria-pressed:font-semibold aria-[current]:not-aria-[current=false]:font-semibold"
|
|
12
|
-
|
|
13
|
-
SHEET_CHIP_ACTIVE = "bg-sky-soft font-semibold"
|
|
14
|
-
|
|
15
|
-
SHEET_MEMBER_CHIP = "ps-1.5 [&_.avatar]:shrink-0 [--avatar-size:28px]"
|
|
16
|
-
|
|
17
|
-
SHEET_DATETIME_INPUT_BASE =
|
|
18
|
-
"min-h-9 px-2 rounded-xl border-0 bg-transparent font-body text-base text-ink tabular-nums cursor-pointer " \
|
|
19
|
-
"outline-none [field-sizing:content] transition-colors hover:bg-paper focus-visible:bg-paper " \
|
|
20
|
-
"[&::-webkit-calendar-picker-indicator]:hidden"
|
|
21
|
-
|
|
22
|
-
SHEET_TEXT_INPUT_BASE =
|
|
23
|
-
"w-full min-w-0 min-h-9 p-0 border-0 bg-transparent font-body text-base text-ink placeholder:text-ink-3 aria-invalid:placeholder:text-berry/70 outline-none"
|
|
24
|
-
|
|
25
|
-
# Makes a <dialog> an iOS sheet on phones (sheet_controller), keeping the dialog's own
|
|
3
|
+
# Makes a <dialog> an iOS sheet on phones (lc-sheet controller), keeping the dialog's own
|
|
26
4
|
# controllers. detents: "fit", "medium large" or "large".
|
|
27
5
|
def lc_sheet_data(data = {}, detents: "fit")
|
|
28
6
|
data.merge(
|
|
29
|
-
controller: [ data[:controller], "sheet" ].compact.join(" "),
|
|
30
|
-
|
|
7
|
+
controller: [ data[:controller], "lc-sheet" ].compact.join(" "),
|
|
8
|
+
lc_sheet_detents_value: detents
|
|
31
9
|
)
|
|
32
10
|
end
|
|
33
|
-
|
|
34
|
-
def lc_sheet_row_class(extra: nil)
|
|
35
|
-
lc_tw(SHEET_ROW_BASE, extra)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def lc_sheet_row_label_class(extra: nil)
|
|
39
|
-
lc_tw(SHEET_ROW_LABEL_BASE, extra)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def lc_sheet_chip_class(active: false, member: false, extra: nil)
|
|
43
|
-
lc_pill_class(size: :sm, active: active, extra: lc_tw(SHEET_CHIP_BASE, (SHEET_CHIP_ACTIVE if active), (SHEET_MEMBER_CHIP if member), extra))
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def lc_sheet_datetime_input_class(extra: nil)
|
|
47
|
-
lc_tw(SHEET_DATETIME_INPUT_BASE, extra)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def lc_sheet_text_input_class(extra: nil)
|
|
51
|
-
lc_tw(SHEET_TEXT_INPUT_BASE, extra)
|
|
52
|
-
end
|
|
53
11
|
end
|
|
54
12
|
end
|
|
@@ -3,104 +3,86 @@ module Lostcoords::Ui
|
|
|
3
3
|
INPUT_TYPES = %i[text search email password tel url number date time datetime-local month week].freeze
|
|
4
4
|
SHEET_DETENTS = [ "fit", "medium large", "large" ].freeze
|
|
5
5
|
|
|
6
|
-
def lc_input(name: nil, value: nil, type: :text, size: :
|
|
6
|
+
def lc_input(name: nil, value: nil, type: :text, size: :md, **attributes)
|
|
7
7
|
type = lc_option!(:type, type, INPUT_TYPES)
|
|
8
8
|
size = lc_option!(:size, size, TailwindHelper::CONTROL_SIZES.keys)
|
|
9
|
-
tag.input(**lc_attributes({ type: type, name: name, value: value, class: lc_input_class(size: size) }, attributes))
|
|
9
|
+
tag.input(**lc_attributes({ type: type, name: name, value: value, class: lc_input_class(size: size), data: { slot: "input" } }, attributes))
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def lc_textarea(content = nil, name: nil, **attributes, &block)
|
|
13
|
-
tag.textarea(block ? capture(&block) : content, **lc_attributes({ name: name, class: lc_textarea_class }, attributes))
|
|
13
|
+
tag.textarea(block ? capture(&block) : content, **lc_attributes({ name: name, class: lc_textarea_class, data: { slot: "textarea" } }, attributes))
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def lc_select(options = nil, name: nil, selected: nil, size: :
|
|
16
|
+
def lc_select(options = nil, name: nil, selected: nil, size: :md, **attributes, &block)
|
|
17
17
|
size = lc_option!(:size, size, TailwindHelper::CONTROL_SIZES.keys)
|
|
18
18
|
content = block ? capture(&block) : options_for_select(options || [], selected)
|
|
19
|
-
tag.select(content, **lc_attributes({ name: name, class: lc_select_class(size: size) }, attributes))
|
|
19
|
+
tag.select(content, **lc_attributes({ name: name, class: lc_select_class(size: size), data: { slot: "select" } }, attributes))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Yields the control's attributes; splat them into the control with `**field`. error: takes a
|
|
23
|
+
# message or a list of messages, such as `record.errors.full_messages_for(:name)`.
|
|
22
24
|
def lc_field(label:, id:, hint: nil, error: nil, **attributes, &block)
|
|
23
25
|
lc_label!(label, :label)
|
|
24
26
|
lc_label!(id, :id)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
errors = Array(error).compact_blank
|
|
28
|
+
descriptions = [ ("#{id}-hint" if hint.present?), ("#{id}-error" if errors.any?) ].compact
|
|
29
|
+
control = { id: id, aria: { describedby: descriptions.presence&.join(" "), invalid: (true if errors.any?) } }
|
|
30
|
+
tag.div(**lc_attributes({ class: lc_field_class, data: { slot: "field", invalid: (true if errors.any?) } }, attributes)) do
|
|
28
31
|
safe_join([
|
|
29
|
-
tag.label(label, for: id, class: lc_field_label_class),
|
|
30
|
-
(capture(control, &block) if block),
|
|
31
|
-
(
|
|
32
|
-
(tag.p(error, id: "#{id}-error", class: "text-sm text-berry", role: "alert") if error.present?)
|
|
32
|
+
tag.label(label, for: id, class: lc_field_label_class, data: { slot: "field-label" }),
|
|
33
|
+
(capture(Parts::Field.new(self, id, control), &block) if block),
|
|
34
|
+
*lc_field_messages(id, hint, errors)
|
|
33
35
|
].compact)
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
def
|
|
38
|
-
tag.div(**lc_attributes({ class: lc_form_actions_class }, attributes), &block)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def lc_segmented(label:, name: nil, value: nil, **attributes, &block)
|
|
39
|
+
def lc_toggle_group(label:, name: nil, value: nil, **attributes, &block)
|
|
42
40
|
lc_label!(label, :label)
|
|
43
|
-
defaults = { class:
|
|
41
|
+
defaults = { class: lc_toggle_group_class, role: "group", aria: { label: label }, data: { slot: "toggle-group", controller: "lc-selection", action: "click->lc-selection#select keydown->lc-selection#navigate", lc_selection_value_value: value } }
|
|
44
42
|
tag.div(**lc_attributes(lc_attributes(defaults, attributes), { role: "group", aria: { label: label } })) do
|
|
45
|
-
safe_join([ (hidden_field_tag(name, value, data: {
|
|
43
|
+
safe_join([ (hidden_field_tag(name, value, data: { lc_selection_target: "input" }) if name), (capture(&block) if block) ].compact)
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
|
|
49
|
-
def
|
|
47
|
+
def lc_toggle_group_item(label = nil, value: nil, selected: false, **attributes, &block)
|
|
50
48
|
value ||= label
|
|
51
49
|
lc_label!(value, :value)
|
|
52
|
-
defaults = { class:
|
|
53
|
-
required = { type: "button", data: {
|
|
50
|
+
defaults = { class: lc_toggle_group_item_class, type: "button", aria: { pressed: selected }, data: { slot: "toggle-group-item", lc_selection_target: "option", value: value } }
|
|
51
|
+
required = { type: "button", data: { lc_selection_target: "option", value: value } }
|
|
54
52
|
tag.button(block ? capture(&block) : label, **lc_attributes(lc_attributes(defaults, attributes), required))
|
|
55
53
|
end
|
|
56
54
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if href
|
|
62
|
-
raise ArgumentError, "Links cannot be disabled; use a pill button instead" if attributes[:disabled] || attributes["disabled"]
|
|
63
|
-
|
|
64
|
-
tag.a(content, **lc_attributes(defaults.merge(href: href, aria: { current: ("page" if active) }), attributes))
|
|
65
|
-
else
|
|
66
|
-
defaults.merge!(type: "button", aria: { pressed: active }, data: { controller: "ui-toggle", action: "click->ui-toggle#toggle" })
|
|
67
|
-
tag.button(content, **lc_attributes(defaults, attributes).merge(type: "button"))
|
|
68
|
-
end
|
|
55
|
+
def lc_toggle(label = nil, active: false, ghost: false, size: :md, **attributes, &block)
|
|
56
|
+
raise ArgumentError, "Use lc_button_link_to for navigation" if attributes.key?(:href) || attributes.key?("href")
|
|
57
|
+
defaults = { class: lc_toggle_class(ghost: ghost, size: size), type: "button", aria: { pressed: active }, data: { slot: "toggle", controller: "lc-toggle", action: "click->lc-toggle#toggle" } }
|
|
58
|
+
tag.button(block ? capture(&block) : label, **lc_attributes(defaults, attributes).merge(type: "button"))
|
|
69
59
|
end
|
|
70
60
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def lc_dialog(id:, title:, trigger: "Open dialog", kind: :modal, **attributes, &block)
|
|
61
|
+
# Yields a builder with footer and close parts.
|
|
62
|
+
def lc_dialog(id:, title:, description: nil, trigger: lc_t("lostcoords.ui.dialog.trigger"), kind: :modal, detents: "fit", **attributes, &block)
|
|
76
63
|
kind = lc_option!(:kind, kind, DialogHelper::DIALOGS.keys)
|
|
77
|
-
lc_render_dialog(id, title, trigger, { class: lc_dialog_class(kind), data: lc_sheet_data }, attributes, &block)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def lc_sheet(id:, title:, trigger: "Open sheet", detents: "fit", **attributes, &block)
|
|
81
64
|
detents = detents.to_s
|
|
82
65
|
raise ArgumentError, "detents must be one of #{SHEET_DETENTS.join(', ')}" unless SHEET_DETENTS.include?(detents)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
lc_render_dialog(id, title, trigger, defaults, attributes, &block)
|
|
66
|
+
defaults = { class: lc_dialog_class(kind), aria: { describedby: ("#{id}-description" if description.present?) }, data: lc_sheet_data({ slot: "dialog" }, detents: detents) }
|
|
67
|
+
lc_render_dialog(id, title, description, trigger, defaults, attributes, &block)
|
|
86
68
|
end
|
|
87
69
|
|
|
88
|
-
def lc_menu(id:, label: "
|
|
70
|
+
def lc_menu(id:, label: lc_t("lostcoords.ui.menu.label"), align: :end, **attributes, &block)
|
|
89
71
|
align = lc_option!(:align, align, MenuHelper::MENU_POPOVER_ALIGN.keys)
|
|
90
|
-
lc_render_panel(id, label, nil, "
|
|
72
|
+
lc_render_panel(id, label, nil, "lc-menu", "menu", lc_menu_popover_class(align: align), lc_attributes({ data: { slot: "menu" } }, attributes), &block)
|
|
91
73
|
end
|
|
92
74
|
|
|
93
|
-
def lc_popover(id:, label: "
|
|
75
|
+
def lc_popover(id:, label: lc_t("lostcoords.ui.popover.trigger"), title: nil, align: :end, trigger_variant: :secondary, **attributes, &block)
|
|
94
76
|
title = title.presence
|
|
95
77
|
align = lc_option!(:align, align, MenuHelper::MENU_POPOVER_ALIGN.keys)
|
|
96
|
-
classes = lc_popover_class(align: align, extra: "data-open:flex flex-col gap-
|
|
97
|
-
lc_render_panel(id, label, title, "
|
|
78
|
+
classes = lc_popover_class(align: align, extra: "data-open:flex flex-col gap-content p-inset z-(--lc-z-popover) min-w-64 max-w-[calc(100vw-2rem)]")
|
|
79
|
+
lc_render_panel(id, label, title, "lc-popover", "dialog", classes, lc_attributes({ data: { slot: "popover" } }, attributes), trigger_variant: trigger_variant, &block)
|
|
98
80
|
end
|
|
99
81
|
|
|
100
82
|
def lc_menu_option(label = nil, href: nil, tone: :default, **attributes, &block)
|
|
101
83
|
tone = lc_option!(:tone, tone, MenuHelper::MENU_OPTION_TONES.keys)
|
|
102
84
|
content = tag.span(block ? capture(&block) : label, class: "col-span-3")
|
|
103
|
-
defaults = { class: lc_menu_option_class(tone: tone), role: "menuitem", data: { action: "click->
|
|
85
|
+
defaults = { class: lc_menu_option_class(tone: tone), role: "menuitem", data: { slot: "menu-item", action: "click->lc-menu#close" } }
|
|
104
86
|
if href
|
|
105
87
|
raise ArgumentError, "Links cannot be disabled; use a menu button instead" if attributes[:disabled] || attributes["disabled"]
|
|
106
88
|
|
|
@@ -110,20 +92,20 @@ module Lostcoords::Ui
|
|
|
110
92
|
end
|
|
111
93
|
end
|
|
112
94
|
|
|
113
|
-
def lc_toast(message = nil, tone: :
|
|
114
|
-
tone = lc_option!(:tone, tone,
|
|
95
|
+
def lc_toast(message = nil, tone: :success, timeout: 4000, **attributes, &block)
|
|
96
|
+
tone = lc_option!(:tone, tone, FeedbackHelper::ALERT_ICONS.keys)
|
|
115
97
|
raise ArgumentError, "timeout must be a nonnegative integer" unless timeout.is_a?(Integer) && timeout >= 0
|
|
116
98
|
|
|
117
|
-
defaults = { class: lc_glass_class(shape: :hud, extra: "flex items-center gap-
|
|
99
|
+
defaults = { class: lc_glass_class(shape: :hud, extra: "flex items-center gap-related px-content py-heading text-label font-semibold transition-opacity data-leaving:opacity-0"), role: tone == :destructive ? "alert" : "status", data: { slot: "toast", tone: tone, controller: "lc-toast", action: "click->lc-toast#dismiss", lc_toast_timeout_value: timeout } }
|
|
118
100
|
tag.div(**lc_attributes(defaults, attributes)) do
|
|
119
|
-
safe_join([
|
|
101
|
+
safe_join([ lc_alert_icon(tone: tone), tag.span(block ? capture(&block) : message) ])
|
|
120
102
|
end
|
|
121
103
|
end
|
|
122
104
|
|
|
123
|
-
def
|
|
105
|
+
def lc_collapsible(title:, open: false, **attributes, &block)
|
|
124
106
|
lc_label!(title, :title)
|
|
125
|
-
defaults = { class: lc_card_class(extra: "
|
|
126
|
-
render "lostcoords/ui/components/
|
|
107
|
+
defaults = { class: lc_card_class(extra: "lc-collapsible"), open: open, data: { slot: "collapsible", controller: "lc-collapsible", action: "click->lc-collapsible#toggle" } }
|
|
108
|
+
render "lostcoords/ui/components/lc_collapsible", title: title, attributes: lc_attributes(defaults, attributes), content: (capture(&block) if block)
|
|
127
109
|
end
|
|
128
110
|
|
|
129
111
|
def lc_emoji_picker(form:, field:, label:, catalog_url:, value: nil, quick: [], recent: [], recent_scope: nil, auto_submit: false, **attributes)
|
|
@@ -133,27 +115,36 @@ module Lostcoords::Ui
|
|
|
133
115
|
end
|
|
134
116
|
|
|
135
117
|
private
|
|
118
|
+
# The hint and error paragraphs under a field or choice group. With an id, controls can point
|
|
119
|
+
# aria-describedby at them.
|
|
120
|
+
def lc_field_messages(id, hint, errors)
|
|
121
|
+
[
|
|
122
|
+
(tag.p(hint, id: ("#{id}-hint" if id), class: "m-0 text-small text-muted-foreground", data: { slot: "field-description" }) if hint.present?),
|
|
123
|
+
(tag.p(safe_join(errors, tag.br), id: ("#{id}-error" if id), class: "m-0 text-small text-destructive", role: "alert", data: { slot: "field-error" }) if errors.any?)
|
|
124
|
+
].compact
|
|
125
|
+
end
|
|
126
|
+
|
|
136
127
|
|
|
137
128
|
def lc_label!(value, name)
|
|
138
129
|
raise ArgumentError, "#{name} must not be blank" if value.blank?
|
|
139
130
|
end
|
|
140
131
|
|
|
141
|
-
def lc_render_dialog(id, title, trigger, defaults, attributes, &block)
|
|
132
|
+
def lc_render_dialog(id, title, description, trigger, defaults, attributes, &block)
|
|
142
133
|
lc_label!(id, :id)
|
|
143
134
|
lc_label!(title, :title)
|
|
144
135
|
lc_label!(trigger, :trigger)
|
|
145
|
-
required = { id: id, aria: { labelledby: "#{id}-title" }, data: (defaults[:data] || {}).merge(
|
|
136
|
+
required = { id: id, aria: { labelledby: "#{id}-title" }, data: (defaults[:data] || {}).merge(lc_dialog_target: "dialog") }
|
|
146
137
|
merged = lc_attributes(lc_attributes(defaults, attributes), required)
|
|
147
|
-
render "lostcoords/ui/components/lc_dialog", id: id, title: title, trigger: trigger, attributes: merged, content: (capture(&block) if block)
|
|
138
|
+
render "lostcoords/ui/components/lc_dialog", id: id, title: title, description: description, trigger: trigger, attributes: merged, content: (capture(Parts::Dialog.new(self, id), &block) if block)
|
|
148
139
|
end
|
|
149
140
|
|
|
150
|
-
def lc_render_panel(id, label, title, controller, role, classes, attributes, &block)
|
|
141
|
+
def lc_render_panel(id, label, title, controller, role, classes, attributes, trigger_variant: :secondary, &block)
|
|
151
142
|
lc_label!(id, :id)
|
|
152
143
|
lc_label!(label, :label)
|
|
153
144
|
required = { id: id, role: role, aria: { labelledby: "#{id}-#{title ? 'title' : 'trigger'}" }, data: { "#{controller}_target" => "popover" } }
|
|
154
145
|
required[:tabindex] = -1 if role == "dialog"
|
|
155
146
|
merged = lc_attributes(lc_attributes({ class: classes }, attributes), required)
|
|
156
|
-
render "lostcoords/ui/components/
|
|
147
|
+
render "lostcoords/ui/components/lc_floating_panel", id: id, label: label, title: title, controller: controller, role: role, trigger_variant: trigger_variant, attributes: merged, content: (capture(&block) if block)
|
|
157
148
|
end
|
|
158
149
|
end
|
|
159
150
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Lostcoords::Ui
|
|
2
|
+
module TableHelper
|
|
3
|
+
def lc_table_class(extra: nil)
|
|
4
|
+
lc_tw("lc-table w-full text-start text-sm", extra)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def lc_table(**attributes, &block)
|
|
8
|
+
tag.table(**lc_attributes({ class: lc_table_class }, attributes), &block)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module TailwindHelper
|
|
3
|
+
# Buttons, inputs, selects, toggles and icon buttons share these heights.
|
|
3
4
|
CONTROL_SIZES = {
|
|
4
|
-
sm: "
|
|
5
|
-
|
|
6
|
-
lg: "
|
|
5
|
+
sm: "h-control-sm",
|
|
6
|
+
md: "h-control-md",
|
|
7
|
+
lg: "h-control-lg"
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
# The theme's own scales, so class merging knows text-label is a size and gap-related a gap.
|
|
11
|
+
MERGE_THEME = {
|
|
12
|
+
"text" => %w[display title heading subheading body small label caption],
|
|
13
|
+
"spacing" => %w[tight related heading content inset section gutter control-sm control-md control-lg],
|
|
14
|
+
"container" => %w[page reading form],
|
|
15
|
+
"radius" => %w[pill]
|
|
7
16
|
}.freeze
|
|
8
17
|
|
|
9
18
|
def lc_tw(*classes)
|
|
10
|
-
@tailwind_merger ||= TailwindMerge::Merger.new
|
|
19
|
+
@tailwind_merger ||= TailwindMerge::Merger.new(config: { theme: MERGE_THEME })
|
|
11
20
|
@tailwind_merger.merge(classes.flatten.compact.join(" "))
|
|
12
21
|
end
|
|
13
22
|
end
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
module Lostcoords::Ui
|
|
2
2
|
module TypographyHelper
|
|
3
|
-
HEADING_SIZES = {
|
|
4
|
-
TEXT_SIZES = { body: "
|
|
5
|
-
TEXT_TONES = { default: "text-
|
|
3
|
+
HEADING_SIZES = { display: "text-display", title: "text-title", heading: "text-heading", subheading: "text-subheading" }.freeze
|
|
4
|
+
TEXT_SIZES = { body: "text-body", small: "text-small", label: "text-label", caption: "text-caption" }.freeze
|
|
5
|
+
TEXT_TONES = { default: "text-foreground", muted: "text-muted-foreground" }.freeze
|
|
6
6
|
|
|
7
|
-
def lc_heading_class(size: :
|
|
7
|
+
def lc_heading_class(size: :heading, trim: false, extra: nil)
|
|
8
8
|
size = lc_option!(:size, size, HEADING_SIZES.keys)
|
|
9
|
-
lc_tw("
|
|
9
|
+
lc_tw("lc-heading m-0 text-foreground", HEADING_SIZES.fetch(size), ("lc-trim" if trim), extra)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def lc_text_class(size: :body, tone: :default, extra: nil)
|
|
12
|
+
def lc_text_class(size: :body, tone: :default, trim: false, extra: nil)
|
|
13
13
|
size = lc_option!(:size, size, TEXT_SIZES.keys)
|
|
14
14
|
tone = lc_option!(:tone, tone, TEXT_TONES.keys)
|
|
15
|
-
lc_tw("
|
|
15
|
+
lc_tw("lc-text m-0", TEXT_SIZES.fetch(size), TEXT_TONES.fetch(tone), ("lc-trim" if trim), extra)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def lc_heading(content = nil, level: 2, size: :
|
|
18
|
+
def lc_heading(content = nil, level: 2, size: :heading, trim: false, **attributes, &block)
|
|
19
19
|
raise ArgumentError, "Invalid level: #{level.inspect}. Expected an integer from 1 to 6" unless (1..6).include?(level) && level.is_a?(Integer)
|
|
20
20
|
|
|
21
|
-
tag.public_send("h#{level}", content, **lc_attributes({ class: lc_heading_class(size: size) }, attributes), &block)
|
|
21
|
+
tag.public_send("h#{level}", content, **lc_attributes({ class: lc_heading_class(size: size, trim: trim) }, attributes), &block)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def lc_text(content = nil, size: :body, tone: :default, **attributes, &block)
|
|
25
|
-
tag.p(content, **lc_attributes({ class: lc_text_class(size: size, tone: tone) }, attributes), &block)
|
|
24
|
+
def lc_text(content = nil, size: :body, tone: :default, trim: false, **attributes, &block)
|
|
25
|
+
tag.p(content, **lc_attributes({ class: lc_text_class(size: size, tone: tone, trim: trim) }, attributes), &block)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["toggle"]
|
|
5
|
+
|
|
6
|
+
toggle() {
|
|
7
|
+
this.setExpanded(!this.element.hasAttribute("data-expanded"))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
copyFailed() {
|
|
11
|
+
this.setExpanded(true)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setExpanded(expanded) {
|
|
15
|
+
this.element.toggleAttribute("data-expanded", expanded)
|
|
16
|
+
if (!this.hasToggleTarget) return
|
|
17
|
+
|
|
18
|
+
this.toggleTarget.setAttribute("aria-expanded", String(expanded))
|
|
19
|
+
this.toggleTarget.textContent = expanded
|
|
20
|
+
? this.toggleTarget.dataset.collapseLabel
|
|
21
|
+
: this.toggleTarget.dataset.expandLabel
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
import { prefersReducedMotion } from "helpers/viewport"
|
|
2
|
+
import { prefersReducedMotion } from "lostcoords/ui/helpers/viewport"
|
|
3
3
|
|
|
4
4
|
// Animates a <details> both ways and keeps what it reveals on screen. Only
|
|
5
5
|
// user toggles go through here: programmatic opens (validation) already scroll
|
|
@@ -40,7 +40,7 @@ export default class extends Controller {
|
|
|
40
40
|
from,
|
|
41
41
|
this.element.offsetHeight,
|
|
42
42
|
duration,
|
|
43
|
-
"--ease-out-expo",
|
|
43
|
+
"--lc-ease-out-expo",
|
|
44
44
|
)
|
|
45
45
|
animation.finished.then(
|
|
46
46
|
() => animation.cancel(),
|
|
@@ -55,7 +55,7 @@ export default class extends Controller {
|
|
|
55
55
|
this.element.toggleAttribute("data-closing", true)
|
|
56
56
|
const duration = Math.max(...this.transitionDurations())
|
|
57
57
|
if (!this.constructor.easesHeightInCss) {
|
|
58
|
-
this.animateHeight(this.element.offsetHeight, this.closedHeight(), duration, "--ease-out")
|
|
58
|
+
this.animateHeight(this.element.offsetHeight, this.closedHeight(), duration, "--lc-ease-out")
|
|
59
59
|
}
|
|
60
60
|
this.closeTimeout = setTimeout(() => {
|
|
61
61
|
this.element.open = false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
import { showContextDialog } from "helpers/context_dialog"
|
|
3
|
-
import { registerDialog } from "helpers/dialog"
|
|
2
|
+
import { showContextDialog } from "lostcoords/ui/helpers/context_dialog"
|
|
3
|
+
import { registerDialog } from "lostcoords/ui/helpers/dialog"
|
|
4
4
|
|
|
5
5
|
export default class extends Controller {
|
|
6
6
|
static targets = ["dialog"]
|
|
@@ -16,8 +16,8 @@ export default class extends Controller {
|
|
|
16
16
|
if (!dialog || dialog.open) return
|
|
17
17
|
|
|
18
18
|
this.activeDialog = dialog
|
|
19
|
-
if (dialog.matches(".
|
|
20
|
-
showContextDialog(dialog, this.element.querySelector("[data-
|
|
19
|
+
if (dialog.matches(".lc-dialog-context")) {
|
|
20
|
+
showContextDialog(dialog, this.element.querySelector("[data-lc-menu-target='popover']"))
|
|
21
21
|
} else {
|
|
22
22
|
dialog.showModal()
|
|
23
23
|
}
|
data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
import { registerDialog } from "helpers/dialog"
|
|
2
|
+
import { registerDialog } from "lostcoords/ui/helpers/dialog"
|
|
3
3
|
|
|
4
4
|
// Opens a dialog whose body is a Turbo frame fetched on open. It reloads on every
|
|
5
5
|
// open, so what it shows is never staler than the page that opened it.
|