view_primitives 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +57 -2
- data/lib/generators/view_primitives/add/add_generator.rb +8 -62
- data/lib/generators/view_primitives/add/templates/accordion/accordion_item_component.rb.tt +30 -11
- data/lib/generators/view_primitives/add/templates/alert/alert_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/alert_dialog/alert_dialog_component.rb.tt +9 -9
- data/lib/generators/view_primitives/add/templates/aspect_ratio/aspect_ratio_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/audio/audio_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/avatar/avatar_component.rb.tt +8 -4
- data/lib/generators/view_primitives/add/templates/badge/badge_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/banner/banner_component.rb.tt +6 -6
- data/lib/generators/view_primitives/add/templates/bottom_nav/bottom_nav_component.rb.tt +11 -4
- data/lib/generators/view_primitives/add/templates/breadcrumb/breadcrumb_component.rb.tt +2 -2
- data/lib/generators/view_primitives/add/templates/button/button_component.rb.tt +8 -5
- data/lib/generators/view_primitives/add/templates/button_group/button_group_component.rb.tt +5 -5
- data/lib/generators/view_primitives/add/templates/calendar/calendar_component.rb.tt +18 -16
- data/lib/generators/view_primitives/add/templates/card/card_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/card/card_footer_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/carousel/carousel_component.rb.tt +26 -13
- data/lib/generators/view_primitives/add/templates/chart/chart_component.rb.tt +10 -4
- data/lib/generators/view_primitives/add/templates/chart/chart_controller.js +26 -3
- data/lib/generators/view_primitives/add/templates/chat_bubble/chat_bubble_component.rb.tt +4 -4
- data/lib/generators/view_primitives/add/templates/checkbox/checkbox_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/collapsible/collapsible_component.rb.tt +12 -5
- data/lib/generators/view_primitives/add/templates/combobox/combobox_component.rb.tt +3 -6
- data/lib/generators/view_primitives/add/templates/command/command_component.rb.tt +22 -18
- data/lib/generators/view_primitives/add/templates/command/command_controller.js +50 -0
- data/lib/generators/view_primitives/add/templates/context_menu/context_menu_component.rb.tt +9 -8
- data/lib/generators/view_primitives/add/templates/data_table/data_table_component.rb.tt +60 -29
- data/lib/generators/view_primitives/add/templates/data_table/data_table_controller.js +2 -2
- data/lib/generators/view_primitives/add/templates/date_picker/date_picker_component.rb.tt +8 -8
- data/lib/generators/view_primitives/add/templates/device_mockup/device_mockup_component.rb.tt +94 -21
- data/lib/generators/view_primitives/add/templates/dialog/dialog_component.rb.tt +13 -10
- data/lib/generators/view_primitives/add/templates/dialog/dialog_controller.js +52 -0
- data/lib/generators/view_primitives/add/templates/drawer/drawer_component.rb.tt +8 -7
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt +5 -6
- data/lib/generators/view_primitives/add/templates/embed/embed_component.rb.tt +2 -2
- data/lib/generators/view_primitives/add/templates/figure/figure_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/file_input/file_input_component.rb.tt +3 -12
- data/lib/generators/view_primitives/add/templates/floating_label/floating_label_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/footer/footer_component.rb.tt +5 -4
- data/lib/generators/view_primitives/add/templates/form_field/form_field_component.rb.tt +18 -5
- data/lib/generators/view_primitives/add/templates/gallery/gallery_component.rb.tt +3 -3
- data/lib/generators/view_primitives/add/templates/gallery/gallery_controller.js +1 -1
- data/lib/generators/view_primitives/add/templates/hover_card/hover_card_component.rb.tt +6 -5
- data/lib/generators/view_primitives/add/templates/iframe/iframe_component.rb.tt +6 -4
- data/lib/generators/view_primitives/add/templates/image/image_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/indicator/indicator_component.rb.tt +5 -4
- data/lib/generators/view_primitives/add/templates/input/input_component.rb.tt +2 -13
- data/lib/generators/view_primitives/add/templates/input_otp/input_otp_component.rb.tt +22 -10
- data/lib/generators/view_primitives/add/templates/kbd/kbd_component.rb.tt +3 -1
- data/lib/generators/view_primitives/add/templates/list_group/list_group_component.rb.tt +6 -2
- data/lib/generators/view_primitives/add/templates/list_group/list_group_item_component.rb.tt +6 -4
- data/lib/generators/view_primitives/add/templates/map_area/map_area_component.rb.tt +3 -2
- data/lib/generators/view_primitives/add/templates/mega_menu/mega_menu_component.rb.tt +9 -9
- data/lib/generators/view_primitives/add/templates/menubar/menubar_component.rb.tt +5 -5
- data/lib/generators/view_primitives/add/templates/menubar/menubar_menu_component.rb.tt +4 -5
- data/lib/generators/view_primitives/add/templates/navbar/navbar_component.rb.tt +51 -11
- data/lib/generators/view_primitives/add/templates/navbar/navbar_controller.js +8 -3
- data/lib/generators/view_primitives/add/templates/navigation_menu/navigation_menu_component.rb.tt +12 -16
- data/lib/generators/view_primitives/add/templates/number_input/number_input_component.rb.tt +4 -11
- data/lib/generators/view_primitives/add/templates/pagination/pagination_component.rb.tt +4 -3
- data/lib/generators/view_primitives/add/templates/picture/picture_component.rb.tt +2 -1
- data/lib/generators/view_primitives/add/templates/popover/popover_component.rb.tt +1 -2
- data/lib/generators/view_primitives/add/templates/progress/progress_component.rb.tt +3 -1
- data/lib/generators/view_primitives/add/templates/qr_code/qr_code_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/radio_group/radio_group_component.rb.tt +8 -5
- data/lib/generators/view_primitives/add/templates/range/range_component.rb.tt +2 -3
- data/lib/generators/view_primitives/add/templates/rating/rating_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/rating_input/rating_controller.js +1 -1
- data/lib/generators/view_primitives/add/templates/rating_input/rating_input_component.rb.tt +4 -3
- data/lib/generators/view_primitives/add/templates/resizable/resizable_component.rb.tt +27 -15
- data/lib/generators/view_primitives/add/templates/scroll_area/scroll_area_component.rb.tt +10 -11
- data/lib/generators/view_primitives/add/templates/search_input/search_input_component.rb.tt +2 -11
- data/lib/generators/view_primitives/add/templates/select/select_component.rb.tt +25 -6
- data/lib/generators/view_primitives/add/templates/separator/separator_component.rb.tt +6 -3
- data/lib/generators/view_primitives/add/templates/sheet/sheet_component.rb.tt +25 -21
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_component.rb.tt +27 -21
- data/lib/generators/view_primitives/add/templates/skeleton/skeleton_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/speed_dial/speed_dial_component.rb.tt +8 -9
- data/lib/generators/view_primitives/add/templates/spinner/spinner_component.rb.tt +15 -6
- data/lib/generators/view_primitives/add/templates/stepper/stepper_component.rb.tt +17 -16
- data/lib/generators/view_primitives/add/templates/switch/switch_component.rb.tt +27 -14
- data/lib/generators/view_primitives/add/templates/tabs/tabs_component.html.erb +13 -7
- data/lib/generators/view_primitives/add/templates/tags_input/tags_input_component.rb.tt +136 -0
- data/lib/generators/view_primitives/add/templates/tags_input/tags_input_controller.js +90 -0
- data/lib/generators/view_primitives/add/templates/textarea/textarea_component.rb.tt +2 -11
- data/lib/generators/view_primitives/add/templates/timeline/timeline_component.rb.tt +9 -7
- data/lib/generators/view_primitives/add/templates/timepicker/timepicker_component.rb.tt +19 -15
- data/lib/generators/view_primitives/add/templates/toaster/toaster_component.rb.tt +10 -10
- data/lib/generators/view_primitives/add/templates/toaster/toaster_controller.js +6 -6
- data/lib/generators/view_primitives/add/templates/toggle/toggle_component.rb.tt +10 -3
- data/lib/generators/view_primitives/add/templates/toggle_group/toggle_group_component.rb.tt +6 -6
- data/lib/generators/view_primitives/add/templates/tooltip/tooltip_component.rb.tt +7 -6
- data/lib/generators/view_primitives/add/templates/video/video_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/wysiwyg/wysiwyg_component.rb.tt +9 -3
- data/lib/generators/view_primitives/component_copier.rb +96 -0
- data/lib/generators/view_primitives/components.rb +16 -2
- data/lib/generators/view_primitives/install/install_generator.rb +13 -3
- data/lib/generators/view_primitives/install/templates/application_component.rb.tt +7 -0
- data/lib/generators/view_primitives/install/templates/styles.rb.tt +26 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/themes/default.css +79 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/themes/rose.css +57 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/tokens.css +46 -0
- data/lib/generators/view_primitives/install/templates/view_primitives/utilities.css +64 -0
- data/lib/generators/view_primitives/install/templates/view_primitives.css +6 -66
- data/lib/generators/view_primitives/list/list_generator.rb +3 -1
- data/lib/generators/view_primitives/theme/theme_generator.rb +79 -0
- data/lib/generators/view_primitives/update/update_generator.rb +112 -0
- data/lib/view_primitives/class_helper.rb +4 -1
- data/lib/view_primitives/railtie.rb +1 -1
- data/lib/view_primitives/version.rb +1 -1
- metadata +12 -4
- data/lib/generators/view_primitives/add/templates/drawer/drawer_controller.js +0 -15
- data/lib/generators/view_primitives/add/templates/sheet/sheet_controller.js +0 -15
|
@@ -10,21 +10,27 @@ module UI
|
|
|
10
10
|
# r.with_panel { right_content }
|
|
11
11
|
# end
|
|
12
12
|
|
|
13
|
-
WRAPPER_CLS = "flex overflow-hidden rounded-
|
|
13
|
+
WRAPPER_CLS = "group flex w-full overflow-hidden rounded-md #{UI::Styles::BORDER} shadow-xs"
|
|
14
14
|
|
|
15
|
-
PANEL_CLS
|
|
15
|
+
PANEL_CLS = "h-full w-full min-h-0 min-w-0 overflow-auto"
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
"bg-border transition-colors hover:bg-ring/30 focus-visible:outline-none " \
|
|
19
|
-
"focus-visible:ring-[3px] focus-visible:ring-ring/50 " \
|
|
20
|
-
"data-[direction=horizontal]:w-px data-[direction=horizontal]:cursor-col-resize " \
|
|
21
|
-
"data-[direction=vertical]:h-px data-[direction=vertical]:cursor-row-resize"
|
|
17
|
+
HANDLE_GRIP_BASE = "z-10 flex shrink-0 items-center justify-center rounded-xs #{UI::Styles::BORDER} bg-border"
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
HANDLE_GRIP_HORIZONTAL = "h-4 w-3"
|
|
20
|
+
HANDLE_GRIP_VERTICAL = "h-3 w-4"
|
|
21
|
+
|
|
22
|
+
HANDLE_HORIZONTAL = "relative flex w-px shrink-0 items-center justify-center bg-border " \
|
|
23
|
+
"after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 " \
|
|
24
|
+
"cursor-col-resize focus-visible:ring-1 focus-visible:ring-ring " \
|
|
25
|
+
"focus-visible:ring-offset-1 focus-visible:outline-hidden"
|
|
26
|
+
|
|
27
|
+
HANDLE_VERTICAL = "relative flex h-px w-full shrink-0 items-center justify-center bg-border " \
|
|
28
|
+
"after:absolute after:inset-x-0 after:top-1/2 after:h-1 after:w-full after:-translate-y-1/2 " \
|
|
29
|
+
"cursor-row-resize focus-visible:ring-1 focus-visible:ring-ring " \
|
|
30
|
+
"focus-visible:ring-offset-1 focus-visible:outline-hidden"
|
|
24
31
|
|
|
25
32
|
renders_many :panels, "UI::ResizableComponent::PanelComponent"
|
|
26
33
|
|
|
27
|
-
# direction: :horizontal (default) | :vertical
|
|
28
34
|
def initialize(direction: :horizontal, **html_attrs)
|
|
29
35
|
@direction = direction.to_sym
|
|
30
36
|
@extra_class = html_attrs.delete(:class)
|
|
@@ -37,6 +43,9 @@ module UI
|
|
|
37
43
|
|
|
38
44
|
content_tag(:div,
|
|
39
45
|
class: cn(WRAPPER_CLS, flex_dir, @extra_class),
|
|
46
|
+
"data-slot": "resizable-panel-group",
|
|
47
|
+
"data-direction": @direction,
|
|
48
|
+
"aria-orientation": @direction == :horizontal ? "horizontal" : "vertical",
|
|
40
49
|
data: {
|
|
41
50
|
controller: "resizable",
|
|
42
51
|
resizable_direction_value: @direction
|
|
@@ -52,18 +61,20 @@ module UI
|
|
|
52
61
|
private
|
|
53
62
|
|
|
54
63
|
def handle
|
|
64
|
+
grip_cls = @direction == :vertical ? HANDLE_GRIP_VERTICAL : HANDLE_GRIP_HORIZONTAL
|
|
65
|
+
handle_cls = @direction == :vertical ? HANDLE_VERTICAL : HANDLE_HORIZONTAL
|
|
66
|
+
|
|
55
67
|
content_tag(:div,
|
|
56
|
-
class:
|
|
57
|
-
|
|
68
|
+
content_tag(:div, nil, class: cn(HANDLE_GRIP_BASE, grip_cls)),
|
|
69
|
+
class: handle_cls,
|
|
70
|
+
"data-slot": "resizable-handle",
|
|
58
71
|
tabindex: "0",
|
|
59
72
|
role: "separator",
|
|
60
73
|
"aria-orientation": @direction == :horizontal ? "vertical" : "horizontal",
|
|
61
74
|
data: {
|
|
62
75
|
resizable_target: "handle",
|
|
63
76
|
action: "mousedown->resizable#startDrag touchstart->resizable#startDrag"
|
|
64
|
-
})
|
|
65
|
-
content_tag(:div, nil, class: HANDLE_GRIP)
|
|
66
|
-
end
|
|
77
|
+
})
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
class PanelComponent < ApplicationComponent
|
|
@@ -75,9 +86,10 @@ module UI
|
|
|
75
86
|
end
|
|
76
87
|
|
|
77
88
|
def call
|
|
78
|
-
style = @default ? "flex: 0 0 #{@default}%" : "flex: 1"
|
|
89
|
+
style = @default ? "flex: 0 0 #{@default}%" : "flex: 1 1 0%"
|
|
79
90
|
content_tag(:div, content,
|
|
80
91
|
class: ResizableComponent::PANEL_CLS,
|
|
92
|
+
"data-slot": "resizable-panel",
|
|
81
93
|
style: style,
|
|
82
94
|
data: {
|
|
83
95
|
resizable_target: "panel",
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class ScrollAreaComponent < ApplicationComponent
|
|
5
|
-
|
|
6
|
-
# Works without a plugin in Tailwind v4 via arbitrary property syntax.
|
|
5
|
+
ROOT = "relative"
|
|
7
6
|
|
|
8
7
|
ORIENTATIONS = {
|
|
9
8
|
vertical: "overflow-y-auto",
|
|
@@ -11,17 +10,16 @@ module UI
|
|
|
11
10
|
both: "overflow-auto"
|
|
12
11
|
}.freeze
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
VIEWPORT = "size-full rounded-[inherit] transition-[color,box-shadow] outline-none " \
|
|
14
|
+
"#{UI::Styles::FOCUS_RING} focus-visible:outline-1"
|
|
15
|
+
|
|
15
16
|
SCROLLBAR_CLS = "[scrollbar-width:thin] " \
|
|
16
17
|
"[scrollbar-color:var(--color-border)_transparent] " \
|
|
17
|
-
"[&::-webkit-scrollbar]:w-
|
|
18
|
+
"[&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar]:h-2.5 " \
|
|
18
19
|
"[&::-webkit-scrollbar-track]:bg-transparent " \
|
|
19
20
|
"[&::-webkit-scrollbar-thumb]:rounded-full " \
|
|
20
21
|
"[&::-webkit-scrollbar-thumb]:bg-border"
|
|
21
22
|
|
|
22
|
-
# orientation: :vertical (default) | :horizontal | :both
|
|
23
|
-
# max_h: Tailwind max-height class, e.g. "max-h-72" (vertical / both)
|
|
24
|
-
# max_w: Tailwind max-width class, e.g. "max-w-sm" (horizontal / both)
|
|
25
23
|
def initialize(orientation: :vertical, max_h: "max-h-72", max_w: nil, **html_attrs)
|
|
26
24
|
@orientation = orientation.to_sym
|
|
27
25
|
@max_h = max_h
|
|
@@ -32,10 +30,11 @@ module UI
|
|
|
32
30
|
|
|
33
31
|
def call
|
|
34
32
|
overflow = ORIENTATIONS.fetch(@orientation, ORIENTATIONS[:vertical])
|
|
35
|
-
content_tag(:div,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
content_tag(:div, class: cn(ROOT, @extra_class), **@html_attrs) do
|
|
34
|
+
content_tag(:div,
|
|
35
|
+
content,
|
|
36
|
+
class: cn(overflow, VIEWPORT, SCROLLBAR_CLS, @max_h, @max_w))
|
|
37
|
+
end
|
|
39
38
|
end
|
|
40
39
|
end
|
|
41
40
|
end
|
|
@@ -5,20 +5,11 @@ module UI
|
|
|
5
5
|
WRAPPER = "relative w-full"
|
|
6
6
|
ICON_WRAP = "pointer-events-none absolute inset-y-0 left-3 flex items-center text-muted-foreground"
|
|
7
7
|
SEARCH_PATH = "m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
|
|
8
|
-
INPUT_BASE = "h-9 w-full min-w-0 rounded-md border border-input bg-transparent py-1 pl-9 pr-3 text-base shadow-xs " \
|
|
9
|
-
"transition-[color,box-shadow] outline-none " \
|
|
10
|
-
"placeholder:text-muted-foreground " \
|
|
11
|
-
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 " \
|
|
12
|
-
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 " \
|
|
13
|
-
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 " \
|
|
14
|
-
"md:text-sm dark:bg-input/30"
|
|
15
|
-
|
|
16
8
|
# placeholder: default "Search…"
|
|
17
9
|
# name / id / value: passed through as html_attrs
|
|
18
10
|
def initialize(placeholder: "Search…", **html_attrs)
|
|
19
11
|
@placeholder = placeholder
|
|
20
|
-
|
|
21
|
-
@html_attrs = html_attrs
|
|
12
|
+
extract_html_attrs(**html_attrs)
|
|
22
13
|
end
|
|
23
14
|
|
|
24
15
|
def call
|
|
@@ -27,7 +18,7 @@ module UI
|
|
|
27
18
|
concat content_tag(:input, nil,
|
|
28
19
|
type: "search",
|
|
29
20
|
placeholder: @placeholder,
|
|
30
|
-
class: cn(
|
|
21
|
+
class: cn(UI::Styles::INPUT, "pl-9 pr-3", @extra_class),
|
|
31
22
|
**@html_attrs)
|
|
32
23
|
end
|
|
33
24
|
end
|
|
@@ -2,27 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class SelectComponent < ApplicationComponent
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
WRAPPER = "group/native-select relative w-fit has-[select:disabled]:opacity-50"
|
|
6
|
+
|
|
7
|
+
BASE = UI::Styles::SELECT
|
|
8
8
|
|
|
9
9
|
# options: array of strings, or [value, label] pairs, or { value: label } hash
|
|
10
|
-
def initialize(options: [], selected: nil, include_blank: false, **html_attrs)
|
|
10
|
+
def initialize(options: [], selected: nil, include_blank: false, size: :default, **html_attrs)
|
|
11
11
|
@options = options
|
|
12
12
|
@selected = selected
|
|
13
13
|
@include_blank = include_blank
|
|
14
|
+
@size = size.to_sym
|
|
14
15
|
@extra_class = html_attrs.delete(:class)
|
|
15
16
|
@html_attrs = html_attrs
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def call
|
|
19
|
-
content_tag(:
|
|
20
|
-
|
|
20
|
+
content_tag(:div, class: WRAPPER) do
|
|
21
|
+
concat content_tag(:select,
|
|
22
|
+
safe_join(option_tags),
|
|
23
|
+
class: cn(BASE, @extra_class),
|
|
24
|
+
"data-size": (@size == :sm ? "sm" : "default"),
|
|
25
|
+
**@html_attrs)
|
|
26
|
+
concat chevron_icon
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
private
|
|
25
31
|
|
|
32
|
+
def chevron_icon
|
|
33
|
+
svg = content_tag(:svg,
|
|
34
|
+
content_tag(:path, nil, d: "m6 9 6 6 6-6", fill: "none", stroke: "currentColor",
|
|
35
|
+
"stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2"),
|
|
36
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
37
|
+
viewBox: "0 0 24 24",
|
|
38
|
+
fill: "none",
|
|
39
|
+
class: "size-4",
|
|
40
|
+
"aria-hidden": "true")
|
|
41
|
+
content_tag(:span, svg,
|
|
42
|
+
class: "pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 text-muted-foreground opacity-50 select-none")
|
|
43
|
+
end
|
|
44
|
+
|
|
26
45
|
def option_tags
|
|
27
46
|
tags = []
|
|
28
47
|
tags << content_tag(:option, "", value: "") if @include_blank
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class SeparatorComponent < ApplicationComponent
|
|
5
|
+
BASE = "shrink-0 bg-border"
|
|
6
|
+
|
|
5
7
|
ORIENTATIONS = {
|
|
6
|
-
horizontal: "
|
|
7
|
-
vertical: "
|
|
8
|
+
horizontal: "h-px w-full",
|
|
9
|
+
vertical: "h-full w-px"
|
|
8
10
|
}.freeze
|
|
9
11
|
|
|
10
12
|
def initialize(orientation: :horizontal, decorative: true, **html_attrs)
|
|
@@ -18,7 +20,8 @@ module UI
|
|
|
18
20
|
content_tag(:div, nil,
|
|
19
21
|
role: (@decorative ? "none" : "separator"),
|
|
20
22
|
"aria-orientation": @orientation.to_s,
|
|
21
|
-
|
|
23
|
+
"data-orientation": @orientation.to_s,
|
|
24
|
+
class: cn(BASE, ORIENTATIONS[@orientation], @extra_class),
|
|
22
25
|
**@html_attrs)
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -5,15 +5,21 @@ module UI
|
|
|
5
5
|
renders_one :trigger
|
|
6
6
|
renders_one :footer
|
|
7
7
|
|
|
8
|
-
OVERLAY =
|
|
8
|
+
OVERLAY = UI::Styles::OVERLAY
|
|
9
9
|
|
|
10
10
|
SIDES = {
|
|
11
|
-
right: "
|
|
12
|
-
left: "
|
|
13
|
-
top: "
|
|
14
|
-
bottom: "
|
|
11
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l border-border sm:max-w-sm",
|
|
12
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r border-border sm:max-w-sm",
|
|
13
|
+
top: "inset-x-0 top-0 h-auto border-b border-border",
|
|
14
|
+
bottom: "inset-x-0 bottom-0 h-auto border-t border-border"
|
|
15
15
|
}.freeze
|
|
16
16
|
|
|
17
|
+
PANEL_BASE = "fixed z-[51] flex flex-col gap-4 bg-background p-6 shadow-lg transition ease-in-out overflow-y-auto"
|
|
18
|
+
|
|
19
|
+
CLOSE_BTN = "absolute top-4 right-4 z-10 rounded-xs opacity-70 ring-offset-background transition-opacity " \
|
|
20
|
+
"hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden " \
|
|
21
|
+
"disabled:pointer-events-none"
|
|
22
|
+
|
|
17
23
|
def initialize(title: nil, description: nil, side: :right, **html_attrs)
|
|
18
24
|
@title = title
|
|
19
25
|
@description = description
|
|
@@ -23,8 +29,8 @@ module UI
|
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
def call
|
|
26
|
-
content_tag(:div, data: { controller: "
|
|
27
|
-
concat content_tag(:span, trigger, data: { action: "click->
|
|
32
|
+
content_tag(:div, data: { controller: "dialog" }, **@html_attrs) do
|
|
33
|
+
concat content_tag(:span, trigger, data: { action: "click->dialog#open" }, class: "contents") if trigger
|
|
28
34
|
concat panel
|
|
29
35
|
end
|
|
30
36
|
end
|
|
@@ -32,23 +38,21 @@ module UI
|
|
|
32
38
|
private
|
|
33
39
|
|
|
34
40
|
def panel
|
|
35
|
-
content_tag(:div, data: {
|
|
41
|
+
content_tag(:div, data: { dialog_target: "panel" }, hidden: true) do
|
|
36
42
|
concat content_tag(:div, nil,
|
|
37
43
|
class: OVERLAY,
|
|
38
|
-
data: { action: "click->
|
|
44
|
+
data: { action: "click->dialog#close" },
|
|
39
45
|
"aria-hidden": "true")
|
|
40
46
|
concat content_tag(:div,
|
|
41
|
-
class: cn(
|
|
42
|
-
SIDES.fetch(@side, SIDES[:right]),
|
|
43
|
-
@extra_class),
|
|
47
|
+
class: cn(PANEL_BASE, SIDES.fetch(@side, SIDES[:right]), @extra_class),
|
|
44
48
|
role: "dialog",
|
|
45
49
|
"aria-modal": "true",
|
|
46
50
|
"aria-label": @title,
|
|
47
|
-
data: { action: "keydown.escape@window->
|
|
51
|
+
data: { action: "keydown.escape@window->dialog#close" }) {
|
|
48
52
|
concat close_button
|
|
49
53
|
concat header_area
|
|
50
|
-
concat content_tag(:div, content, class: "flex-1
|
|
51
|
-
concat content_tag(:div, footer, class: "mt-
|
|
54
|
+
concat content_tag(:div, content, class: "flex-1") unless content.blank?
|
|
55
|
+
concat content_tag(:div, footer, class: "mt-auto flex flex-col gap-2") if footer
|
|
52
56
|
}
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -56,9 +60,9 @@ module UI
|
|
|
56
60
|
def header_area
|
|
57
61
|
return "" if @title.nil? && @description.nil?
|
|
58
62
|
|
|
59
|
-
content_tag(:div, class: "
|
|
60
|
-
concat content_tag(:h2, @title, class: "
|
|
61
|
-
concat content_tag(:p, @description, class: "
|
|
63
|
+
content_tag(:div, class: "flex flex-col gap-1.5 pr-6") do
|
|
64
|
+
concat content_tag(:h2, @title, class: "font-semibold text-foreground") if @title
|
|
65
|
+
concat content_tag(:p, @description, class: "text-sm text-muted-foreground") if @description
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
|
|
@@ -66,13 +70,13 @@ module UI
|
|
|
66
70
|
content_tag(:button,
|
|
67
71
|
close_svg,
|
|
68
72
|
type: "button",
|
|
69
|
-
class:
|
|
70
|
-
data: { action: "click->
|
|
73
|
+
class: CLOSE_BTN,
|
|
74
|
+
data: { action: "click->dialog#close" },
|
|
71
75
|
"aria-label": "Close")
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def close_svg
|
|
75
|
-
raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>')
|
|
79
|
+
raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>')
|
|
76
80
|
end
|
|
77
81
|
end
|
|
78
82
|
end
|
|
@@ -13,31 +13,37 @@ module UI
|
|
|
13
13
|
# end
|
|
14
14
|
|
|
15
15
|
RAIL_CLS = "group peer fixed inset-y-0 left-0 z-30 flex h-full flex-col " \
|
|
16
|
-
"border-r border-border bg-
|
|
16
|
+
"border-r border-sidebar-border bg-sidebar text-sidebar-foreground " \
|
|
17
|
+
"transition-[width] duration-300 " \
|
|
17
18
|
"data-[collapsed=true]:w-16 data-[collapsed=false]:w-64"
|
|
18
19
|
|
|
19
|
-
HEADER_CLS = "flex h-14 items-center justify-between border-b border-border px-4"
|
|
20
|
+
HEADER_CLS = "flex h-14 shrink-0 items-center justify-between border-b border-sidebar-border px-4 " \
|
|
21
|
+
"group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-2"
|
|
20
22
|
|
|
21
|
-
TOGGLE_CLS = "inline-flex size-
|
|
22
|
-
"text-
|
|
23
|
-
"
|
|
23
|
+
TOGGLE_CLS = "inline-flex size-7 shrink-0 items-center justify-center rounded-md " \
|
|
24
|
+
"text-sidebar-foreground ring-sidebar-ring outline-hidden " \
|
|
25
|
+
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground " \
|
|
26
|
+
"focus-visible:ring-2 transition"
|
|
24
27
|
|
|
25
|
-
NAV_CLS = "flex-1 overflow-y-auto px-2 py-3"
|
|
28
|
+
NAV_CLS = "flex flex-1 flex-col gap-1 overflow-y-auto px-2 py-3"
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
"transition-opacity group-data-[collapsed=true]:opacity-0 group-data-[collapsed=true]:h-0 " \
|
|
29
|
-
"group-data-[collapsed=true]:overflow-hidden group-data-[collapsed=true]:mb-0"
|
|
30
|
+
GROUP_WRAP = "mb-4 last:mb-0 group-data-[collapsed=true]:mb-0"
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:gap-0 " \
|
|
34
|
-
"hover:bg-accent hover:text-accent-foreground " \
|
|
35
|
-
"aria-[current]:bg-accent aria-[current]:text-foreground aria-[current]:font-semibold " \
|
|
36
|
-
"focus-visible:ring-[3px] focus-visible:ring-ring/50 outline-none"
|
|
32
|
+
GROUP_LABEL = "mb-1 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium " \
|
|
33
|
+
"text-sidebar-foreground/70 group-data-[collapsed=true]:hidden"
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
ITEM_CLS = "flex w-full items-center gap-2 overflow-hidden rounded-md p-2 " \
|
|
36
|
+
"text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] " \
|
|
37
|
+
"group-data-[collapsed=true]:size-8 group-data-[collapsed=true]:justify-center " \
|
|
38
|
+
"group-data-[collapsed=true]:gap-0 group-data-[collapsed=true]:p-2 " \
|
|
39
|
+
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground " \
|
|
40
|
+
"focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground " \
|
|
41
|
+
"aria-[current]:bg-sidebar-accent aria-[current]:font-medium " \
|
|
42
|
+
"aria-[current]:text-sidebar-accent-foreground [&>span:last-child]:truncate " \
|
|
43
|
+
"[&_svg]:size-4 [&_svg]:shrink-0"
|
|
44
|
+
|
|
45
|
+
ITEM_LABEL = "whitespace-nowrap transition-[opacity,width] " \
|
|
46
|
+
"group-data-[collapsed=true]:hidden"
|
|
41
47
|
|
|
42
48
|
renders_many :groups, "UI::SidebarComponent::GroupComponent"
|
|
43
49
|
renders_many :items, "UI::SidebarComponent::ItemComponent"
|
|
@@ -82,7 +88,7 @@ module UI
|
|
|
82
88
|
def nav_body
|
|
83
89
|
content_tag(:nav, class: NAV_CLS) do
|
|
84
90
|
concat safe_join(groups) if groups.any?
|
|
85
|
-
concat content_tag(:div, safe_join(items), class: "
|
|
91
|
+
concat content_tag(:div, safe_join(items), class: "flex flex-col gap-0.5") if items.any?
|
|
86
92
|
concat content if content?
|
|
87
93
|
end
|
|
88
94
|
end
|
|
@@ -105,9 +111,9 @@ module UI
|
|
|
105
111
|
end
|
|
106
112
|
|
|
107
113
|
def call
|
|
108
|
-
content_tag(:div, class:
|
|
114
|
+
content_tag(:div, class: SidebarComponent::GROUP_WRAP, **@html_attrs) do
|
|
109
115
|
concat content_tag(:p, @label, class: SidebarComponent::GROUP_LABEL) if @label
|
|
110
|
-
concat content_tag(:div, safe_join(items), class: "
|
|
116
|
+
concat content_tag(:div, safe_join(items), class: "flex flex-col gap-0.5")
|
|
111
117
|
concat content if content?
|
|
112
118
|
end
|
|
113
119
|
end
|
|
@@ -10,18 +10,17 @@ module UI
|
|
|
10
10
|
# dial.with_action(label: "Upload", icon: :upload, data: { action: "..." })
|
|
11
11
|
# end
|
|
12
12
|
|
|
13
|
-
FAB_CLS = "relative z-50 inline-flex size-
|
|
14
|
-
"bg-primary text-primary-foreground shadow-
|
|
15
|
-
"
|
|
16
|
-
"focus-visible:ring-ring/50 active:scale-95"
|
|
13
|
+
FAB_CLS = "relative z-50 inline-flex size-12 items-center justify-center rounded-full " \
|
|
14
|
+
"bg-primary text-primary-foreground shadow-xs transition-all outline-none " \
|
|
15
|
+
"#{UI::Styles::FOCUS_RING} active:scale-95"
|
|
17
16
|
|
|
18
|
-
PANEL_CLS = "absolute bottom-
|
|
17
|
+
PANEL_CLS = "absolute bottom-14 right-0 flex flex-col-reverse items-end gap-2"
|
|
19
18
|
|
|
20
|
-
ACTION_CLS = "flex items-center gap-2 rounded-full bg-background px-4 py-2 text-sm font-medium " \
|
|
21
|
-
"
|
|
19
|
+
ACTION_CLS = "flex items-center gap-2 rounded-full border border-input bg-background px-4 py-2 text-sm font-medium " \
|
|
20
|
+
"whitespace-nowrap shadow-xs transition-all outline-none " \
|
|
22
21
|
"hover:bg-accent hover:text-accent-foreground " \
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"#{UI::Styles::FOCUS_RING} " \
|
|
23
|
+
"dark:border-input dark:bg-input/30 dark:hover:bg-input/50"
|
|
25
24
|
|
|
26
25
|
PLUS_PATH = "M12 5v14M5 12h14"
|
|
27
26
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module UI
|
|
4
4
|
class SpinnerComponent < ApplicationComponent
|
|
5
|
-
|
|
5
|
+
LOADER_PATH = "M21 12a9 9 0 1 1-6.219-8.56"
|
|
6
6
|
|
|
7
7
|
SIZES = {
|
|
8
8
|
sm: "size-4",
|
|
9
|
-
default: "size-
|
|
10
|
-
lg: "size-
|
|
9
|
+
default: "size-4",
|
|
10
|
+
lg: "size-6"
|
|
11
11
|
}.freeze
|
|
12
12
|
|
|
13
13
|
def initialize(size: :default, **html_attrs)
|
|
@@ -17,10 +17,19 @@ module UI
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def call
|
|
20
|
-
content_tag(:
|
|
21
|
-
content_tag(:
|
|
22
|
-
|
|
20
|
+
content_tag(:svg,
|
|
21
|
+
content_tag(:path, nil,
|
|
22
|
+
d: LOADER_PATH,
|
|
23
|
+
"stroke-linecap": "round",
|
|
24
|
+
"stroke-linejoin": "round"),
|
|
25
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
26
|
+
viewBox: "0 0 24 24",
|
|
27
|
+
fill: "none",
|
|
28
|
+
stroke: "currentColor",
|
|
29
|
+
"stroke-width": "2",
|
|
30
|
+
class: cn("animate-spin text-muted-foreground", SIZES.fetch(@size, SIZES[:default]), @extra_class),
|
|
23
31
|
role: "status",
|
|
32
|
+
"aria-label": "Loading",
|
|
24
33
|
**@html_attrs)
|
|
25
34
|
end
|
|
26
35
|
end
|
|
@@ -18,6 +18,7 @@ module UI
|
|
|
18
18
|
content_tag(:ol,
|
|
19
19
|
class: cn(wrapper_class, @extra_class),
|
|
20
20
|
"aria-label": "Progress",
|
|
21
|
+
data: { slot: "stepper" },
|
|
21
22
|
**@html_attrs) do
|
|
22
23
|
safe_join(@steps.each_with_index.map { |step, i| step_item(step, i) })
|
|
23
24
|
end
|
|
@@ -30,47 +31,47 @@ module UI
|
|
|
30
31
|
status = step.fetch(:status, :pending).to_sym
|
|
31
32
|
|
|
32
33
|
if @orientation == :vertical
|
|
33
|
-
vertical_item(step, status, is_last)
|
|
34
|
+
vertical_item(step, status, is_last, index)
|
|
34
35
|
else
|
|
35
|
-
horizontal_item(step, status, is_last)
|
|
36
|
+
horizontal_item(step, status, is_last, index)
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
def horizontal_item(step, status, is_last)
|
|
40
|
+
def horizontal_item(step, status, is_last, index)
|
|
40
41
|
content_tag(:li, class: "flex items-center #{is_last ? '' : 'flex-1'}") do
|
|
41
|
-
concat step_circle(status,
|
|
42
|
-
concat content_tag(:p, step[:label], class: cn("ml-
|
|
42
|
+
concat step_circle(status, index + 1)
|
|
43
|
+
concat content_tag(:p, step[:label], class: cn("ml-3 text-sm font-medium whitespace-nowrap", label_color(status))) unless step[:label].nil?
|
|
43
44
|
concat connector(:horizontal, status) unless is_last
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
def vertical_item(step, status, is_last)
|
|
48
|
+
def vertical_item(step, status, is_last, index)
|
|
48
49
|
content_tag(:li, class: "relative flex gap-4") do
|
|
49
50
|
concat content_tag(:div, class: "flex flex-col items-center") {
|
|
50
|
-
concat step_circle(status,
|
|
51
|
+
concat step_circle(status, index + 1)
|
|
51
52
|
concat connector(:vertical, status) unless is_last
|
|
52
53
|
}
|
|
53
|
-
concat content_tag(:div, class: "pb-
|
|
54
|
+
concat content_tag(:div, class: "min-w-0 pb-8 pt-0.5") {
|
|
54
55
|
concat content_tag(:p, step[:label], class: cn("text-sm font-medium", label_color(status)))
|
|
55
56
|
concat content_tag(:p, step[:description], class: "mt-0.5 text-xs text-muted-foreground") if step[:description]
|
|
56
57
|
}
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
def step_circle(status,
|
|
61
|
-
base = "flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-
|
|
61
|
+
def step_circle(status, number)
|
|
62
|
+
base = "flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-xs font-medium transition-colors"
|
|
62
63
|
case status
|
|
63
64
|
when :complete
|
|
64
65
|
content_tag(:span, check_svg,
|
|
65
66
|
class: cn(base, "border-primary bg-primary text-primary-foreground"),
|
|
66
67
|
"aria-label": "Completed")
|
|
67
68
|
when :current
|
|
68
|
-
content_tag(:span,
|
|
69
|
-
class: cn(base, "border-primary text-primary"),
|
|
69
|
+
content_tag(:span, number.to_s,
|
|
70
|
+
class: cn(base, "border-primary bg-background text-primary ring-[3px] ring-ring/50"),
|
|
70
71
|
"aria-current": "step")
|
|
71
72
|
else
|
|
72
|
-
content_tag(:span,
|
|
73
|
-
class: cn(base, "border-muted
|
|
73
|
+
content_tag(:span, number.to_s,
|
|
74
|
+
class: cn(base, "border-border bg-muted text-muted-foreground"),
|
|
74
75
|
"aria-label": "Pending")
|
|
75
76
|
end
|
|
76
77
|
end
|
|
@@ -78,9 +79,9 @@ module UI
|
|
|
78
79
|
def connector(direction, status)
|
|
79
80
|
filled = status == :complete
|
|
80
81
|
if direction == :horizontal
|
|
81
|
-
content_tag(:div, nil, class: cn("h-0.5 flex-1
|
|
82
|
+
content_tag(:div, nil, class: cn("mx-3 h-0.5 min-w-8 flex-1", filled ? "bg-primary" : "bg-border"))
|
|
82
83
|
else
|
|
83
|
-
content_tag(:div, nil, class: cn("w-0.5 flex-1
|
|
84
|
+
content_tag(:div, nil, class: cn("my-1 w-0.5 flex-1", filled ? "bg-primary" : "bg-border"))
|
|
84
85
|
end
|
|
85
86
|
end
|
|
86
87
|
|