phlex_ui 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/phlex_ui/accordion/content.rb +21 -0
- data/lib/phlex_ui/accordion/default_content.rb +17 -0
- data/lib/phlex_ui/accordion/default_trigger.rb +19 -0
- data/lib/phlex_ui/accordion/icon.rb +38 -0
- data/lib/phlex_ui/accordion/item.rb +28 -0
- data/lib/phlex_ui/accordion/trigger.rb +16 -0
- data/lib/phlex_ui/accordion.rb +28 -0
- data/lib/phlex_ui/alert/description.rb +17 -0
- data/lib/phlex_ui/alert/title.rb +17 -0
- data/lib/phlex_ui/alert.rb +36 -0
- data/lib/phlex_ui/alert_dialog/action.rb +17 -0
- data/lib/phlex_ui/alert_dialog/cancel.rb +21 -0
- data/lib/phlex_ui/alert_dialog/content.rb +45 -0
- data/lib/phlex_ui/alert_dialog/description.rb +17 -0
- data/lib/phlex_ui/alert_dialog/footer.rb +17 -0
- data/lib/phlex_ui/alert_dialog/header.rb +17 -0
- data/lib/phlex_ui/alert_dialog/title.rb +17 -0
- data/lib/phlex_ui/alert_dialog/trigger.rb +18 -0
- data/lib/phlex_ui/alert_dialog.rb +26 -0
- data/lib/phlex_ui/aspect_ratio.rb +33 -0
- data/lib/phlex_ui/attribute_merger.rb +75 -0
- data/lib/phlex_ui/avatar/fallback.rb +17 -0
- data/lib/phlex_ui/avatar/image.rb +26 -0
- data/lib/phlex_ui/avatar.rb +49 -0
- data/lib/phlex_ui/badge.rb +60 -0
- data/lib/phlex_ui/base.rb +24 -0
- data/lib/phlex_ui/button.rb +85 -16
- data/lib/phlex_ui/card/content.rb +17 -0
- data/lib/phlex_ui/card/description.rb +17 -0
- data/lib/phlex_ui/card/footer.rb +17 -0
- data/lib/phlex_ui/card/header.rb +17 -0
- data/lib/phlex_ui/card/title.rb +17 -0
- data/lib/phlex_ui/card.rb +17 -0
- data/lib/phlex_ui/checkbox.rb +18 -0
- data/lib/phlex_ui/clipboard/popover.rb +36 -0
- data/lib/phlex_ui/clipboard/source.rb +19 -0
- data/lib/phlex_ui/clipboard/trigger.rb +20 -0
- data/lib/phlex_ui/clipboard.rb +39 -0
- data/lib/phlex_ui/codeblock.rb +105 -0
- data/lib/phlex_ui/collapsible/content.rb +18 -0
- data/lib/phlex_ui/collapsible/trigger.rb +19 -0
- data/lib/phlex_ui/collapsible.rb +25 -0
- data/lib/phlex_ui/context_menu/content.rb +25 -0
- data/lib/phlex_ui/context_menu/item.rb +66 -0
- data/lib/phlex_ui/context_menu/label.rb +24 -0
- data/lib/phlex_ui/context_menu/separator.rb +19 -0
- data/lib/phlex_ui/context_menu/trigger.rb +20 -0
- data/lib/phlex_ui/context_menu.rb +26 -0
- data/lib/phlex_ui/dialog/content.rb +78 -0
- data/lib/phlex_ui/dialog/description.rb +17 -0
- data/lib/phlex_ui/dialog/footer.rb +17 -0
- data/lib/phlex_ui/dialog/header.rb +17 -0
- data/lib/phlex_ui/dialog/middle.rb +17 -0
- data/lib/phlex_ui/dialog/title.rb +17 -0
- data/lib/phlex_ui/dialog/trigger.rb +19 -0
- data/lib/phlex_ui/dialog.rb +25 -0
- data/lib/phlex_ui/dropdown_menu/content.rb +22 -0
- data/lib/phlex_ui/dropdown_menu/item.rb +28 -0
- data/lib/phlex_ui/dropdown_menu/label.rb +17 -0
- data/lib/phlex_ui/dropdown_menu/separator.rb +19 -0
- data/lib/phlex_ui/dropdown_menu/trigger.rb +17 -0
- data/lib/phlex_ui/dropdown_menu.rb +26 -0
- data/lib/phlex_ui/form/item.rb +17 -0
- data/lib/phlex_ui/form/spacer.rb +17 -0
- data/lib/phlex_ui/form.rb +34 -0
- data/lib/phlex_ui/hint.rb +17 -0
- data/lib/phlex_ui/hover_card/content.rb +22 -0
- data/lib/phlex_ui/hover_card/trigger.rb +19 -0
- data/lib/phlex_ui/hover_card.rb +27 -0
- data/lib/phlex_ui/input.rb +29 -0
- data/lib/phlex_ui/input_error.rb +17 -0
- data/lib/phlex_ui/label.rb +17 -0
- data/lib/phlex_ui/link.rb +97 -0
- data/lib/phlex_ui/popover/content.rb +22 -0
- data/lib/phlex_ui/popover/trigger.rb +19 -0
- data/lib/phlex_ui/popover.rb +25 -0
- data/lib/phlex_ui/shortcut_key.rb +17 -0
- data/lib/phlex_ui/table/body.rb +17 -0
- data/lib/phlex_ui/table/builder.rb +77 -0
- data/lib/phlex_ui/table/caption.rb +17 -0
- data/lib/phlex_ui/table/cell.rb +17 -0
- data/lib/phlex_ui/table/footer.rb +17 -0
- data/lib/phlex_ui/table/head.rb +17 -0
- data/lib/phlex_ui/table/header.rb +17 -0
- data/lib/phlex_ui/table/row.rb +17 -0
- data/lib/phlex_ui/table.rb +19 -0
- data/lib/phlex_ui/tabs/content.rb +26 -0
- data/lib/phlex_ui/tabs/list.rb +17 -0
- data/lib/phlex_ui/tabs/trigger.rb +28 -0
- data/lib/phlex_ui/tabs.rb +25 -0
- data/lib/phlex_ui/theme_toggle.rb +41 -0
- data/lib/phlex_ui/tooltip/content.rb +22 -0
- data/lib/phlex_ui/tooltip/trigger.rb +17 -0
- data/lib/phlex_ui/tooltip.rb +25 -0
- data/lib/phlex_ui/typography/blockquote.rb +17 -0
- data/lib/phlex_ui/typography/h1.rb +17 -0
- data/lib/phlex_ui/typography/h2.rb +17 -0
- data/lib/phlex_ui/typography/h3.rb +17 -0
- data/lib/phlex_ui/typography/h4.rb +17 -0
- data/lib/phlex_ui/typography/inline_code.rb +17 -0
- data/lib/phlex_ui/typography/large.rb +17 -0
- data/lib/phlex_ui/typography/lead.rb +17 -0
- data/lib/phlex_ui/typography/list.rb +47 -0
- data/lib/phlex_ui/typography/list_item.rb +17 -0
- data/lib/phlex_ui/typography/muted.rb +17 -0
- data/lib/phlex_ui/typography/p.rb +17 -0
- data/lib/phlex_ui/typography/small.rb +17 -0
- data/lib/phlex_ui.rb +0 -8
- metadata +113 -28
- data/tasks/install.rake +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 997466eeaf90ee785e93f2bbb17ddf3014db28e538cfd2b5bb1177800b7eff41
|
4
|
+
data.tar.gz: a876736cf42046f4eda6e974ca8d48a2ffceb29dc49679bf2a857815a2d14c4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca6af73e57c9c22983f4eab367a894bbcdba53e8b16b707ec14accf80f1faa81994f45f2ea34ffb1867cc800875bce96c7d716043f418b1aa88aa1adfcc3049d
|
7
|
+
data.tar.gz: 6fb432282362714d0a1b048e476dc15d7ad38f60ef26c311eaeefe35fcca31b9b34b72224f61147b991fd832c148f1380fa556eb878abe5ecf27dd3a50c8ea46
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion::Content < Base
|
5
|
+
def template(&)
|
6
|
+
div(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
data: {
|
14
|
+
accordion_target: "content"
|
15
|
+
},
|
16
|
+
class: 'overflow-y-hidden',
|
17
|
+
style: 'height: 0px;'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion::DefaultTrigger < Base
|
5
|
+
def template(&)
|
6
|
+
div(class: "flex items-center justify-between w-full") do
|
7
|
+
p(&)
|
8
|
+
render ::PhlexUI::Accordion::Icon.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_attrs
|
13
|
+
{
|
14
|
+
data: { action: "click->accordion#toggle" },
|
15
|
+
class: 'w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline'
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion::Icon < Base
|
5
|
+
def template(&block)
|
6
|
+
span(**attrs) do
|
7
|
+
if block
|
8
|
+
block.call
|
9
|
+
else
|
10
|
+
icon
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def icon
|
16
|
+
svg(
|
17
|
+
xmlns: "http://www.w3.org/2000/svg",
|
18
|
+
viewbox: "0 0 20 20",
|
19
|
+
fill: "currentColor",
|
20
|
+
class: "w-4 h-4"
|
21
|
+
) do |s|
|
22
|
+
s.path(
|
23
|
+
fill_rule: "evenodd",
|
24
|
+
d:
|
25
|
+
"M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z",
|
26
|
+
clip_rule: "evenodd"
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_attrs
|
32
|
+
{
|
33
|
+
class: 'opacity-50',
|
34
|
+
data: { accordion_target: "icon" },
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion::Item < Base
|
5
|
+
def initialize(open: false, rotate_icon: 180, **attrs)
|
6
|
+
@open = open
|
7
|
+
@rotate_icon = rotate_icon
|
8
|
+
super(**attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def template(&)
|
12
|
+
div(**attrs, &)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def default_attrs
|
18
|
+
{
|
19
|
+
data: {
|
20
|
+
controller: "accordion",
|
21
|
+
accordion_open_value: @open,
|
22
|
+
accordion_rotate_icon_value: @rotate_icon
|
23
|
+
},
|
24
|
+
class: 'border-b'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion::Trigger < Base
|
5
|
+
def template(&)
|
6
|
+
button(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_attrs
|
10
|
+
{
|
11
|
+
data: { action: "click->accordion#toggle" },
|
12
|
+
class: 'w-full flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Accordion < Base
|
5
|
+
def template(&)
|
6
|
+
div(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_item(title, content, open: false)
|
10
|
+
render Accordion::Item.new(open: open) do
|
11
|
+
render PhlexUI::Accordion::Trigger.new do
|
12
|
+
render PhlexUI::Accordion::DefaultTrigger.new { title }
|
13
|
+
end
|
14
|
+
render PhlexUI::Accordion::Content.new do
|
15
|
+
render PhlexUI::Accordion::DefaultContent.new { content }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def default_attrs
|
23
|
+
{
|
24
|
+
class: 'w-full'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Alert < Base
|
5
|
+
def initialize(variant: nil, **attrs)
|
6
|
+
@variant = variant
|
7
|
+
super(**attrs) # must be called after variant is set
|
8
|
+
end
|
9
|
+
|
10
|
+
def template(&)
|
11
|
+
div(**attrs, &)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def colors
|
17
|
+
case @variant
|
18
|
+
when nil
|
19
|
+
'ring-border bg-muted-background text-text [&>svg]:opacity-80'
|
20
|
+
when :warning
|
21
|
+
'ring-warning/20 bg-warning/10 text-warning [&>svg]:text-warning/80'
|
22
|
+
when :success
|
23
|
+
'ring-success/20 bg-success/10 text-success [&>svg]:text-success/80'
|
24
|
+
when :destructive
|
25
|
+
'ring-destructive/10 dark:ring-destructive/20 text-destructive bg-destructive/10 [&>svg]:text-destructive/80'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_attrs
|
30
|
+
base_classes = 'relative w-full ring-1 ring-inset rounded-lg px-4 py-4 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-8'
|
31
|
+
{
|
32
|
+
class: tokens(base_classes, colors),
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog::Cancel < Base
|
5
|
+
def template(&)
|
6
|
+
render PhlexUI::Button.new(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
variant: :outline,
|
14
|
+
data: {
|
15
|
+
action: 'click->dismissable#dismiss'
|
16
|
+
},
|
17
|
+
class: 'mt-2 sm:mt-0'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog::Content < Base
|
5
|
+
def template(&)
|
6
|
+
template_tag(**attrs) do
|
7
|
+
div(data: { controller: 'dismissable' }) do
|
8
|
+
background
|
9
|
+
container(&)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def background
|
15
|
+
div(
|
16
|
+
data_state: "open",
|
17
|
+
class:
|
18
|
+
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
19
|
+
style: "pointer-events:auto",
|
20
|
+
data_aria_hidden: "true",
|
21
|
+
aria_hidden: "true"
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def container(&)
|
26
|
+
div(
|
27
|
+
role: "alertdialog",
|
28
|
+
data_state: "open",
|
29
|
+
class: "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
30
|
+
style: "pointer-events:auto",
|
31
|
+
&
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def default_attrs
|
38
|
+
{
|
39
|
+
data: {
|
40
|
+
alert_dialog_target: "content"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog::Footer < Base
|
5
|
+
def template(&)
|
6
|
+
div(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
class: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog::Header < Base
|
5
|
+
def template(&)
|
6
|
+
div(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
class: "flex flex-col space-y-2 text-center sm:text-left"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog::Trigger < Base
|
5
|
+
def template(&)
|
6
|
+
div(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
data: { action: "click->alert-dialog#open" },
|
14
|
+
class: 'inline-block'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AlertDialog < Base
|
5
|
+
def initialize(open: false, **attrs)
|
6
|
+
@open = open
|
7
|
+
super(**attrs)
|
8
|
+
end
|
9
|
+
|
10
|
+
def template(&)
|
11
|
+
div(**attrs, &)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def default_attrs
|
17
|
+
{
|
18
|
+
data: {
|
19
|
+
controller: 'alert-dialog',
|
20
|
+
alert_dialog_open_value: @open
|
21
|
+
},
|
22
|
+
class: 'inline-block'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class AspectRatio < Base
|
5
|
+
def initialize(aspect_ratio: "16/9", **attrs)
|
6
|
+
raise "aspect_ratio must be in the format of a string with a slash in the middle (eg. '16/9', '1/1')" unless aspect_ratio.is_a?(String) && aspect_ratio.include?("/")
|
7
|
+
|
8
|
+
@aspect_ratio = aspect_ratio
|
9
|
+
super(**attrs)
|
10
|
+
end
|
11
|
+
|
12
|
+
def template(&)
|
13
|
+
div(
|
14
|
+
class: "relative w-full",
|
15
|
+
style: "padding-bottom: #{padding_bottom}%;",
|
16
|
+
) do
|
17
|
+
div(**attrs, &)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def padding_bottom
|
24
|
+
@aspect_ratio.split("/").map(&:to_i).reverse.reduce(&:fdiv) * 100
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_attrs
|
28
|
+
{
|
29
|
+
class: "bg-muted-background absolute inset-0 [&>img]:object-cover [&>img]:absolute [&>img]:h-full [&>img]:w-full [&>img]:inset-0 [&>img]:text-transparent"
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module PhlexUI
|
2
|
+
class AttributeMerger
|
3
|
+
attr_reader :default_attrs, :user_attrs
|
4
|
+
OVERRIDE_KEY = '!'.freeze
|
5
|
+
|
6
|
+
def initialize(default_attrs, user_attrs)
|
7
|
+
@default_attrs = flatten_hash(default_attrs)
|
8
|
+
@user_attrs = flatten_hash(user_attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
merged_attrs = merge_hashes(default_attrs, non_override_attrs)
|
13
|
+
mix(merged_attrs, override_attrs)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# @return [Hash]
|
19
|
+
def mix(*args)
|
20
|
+
args.each_with_object({}) do |object, result|
|
21
|
+
result.merge!(object) do |_key, old, new|
|
22
|
+
case new
|
23
|
+
when Hash
|
24
|
+
old.is_a?(Hash) ? mix(old, new) : new
|
25
|
+
when Array
|
26
|
+
old.is_a?(Array) ? (old + new) : new
|
27
|
+
when String
|
28
|
+
old.is_a?(String) ? "#{old} #{new}" : new
|
29
|
+
else
|
30
|
+
new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
result.transform_keys! do |key|
|
35
|
+
key.end_with?("!") ? key.name.chop.to_sym : key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def override_attrs
|
41
|
+
user_attrs.select do |key, value|
|
42
|
+
key.to_s.include?(OVERRIDE_KEY)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def non_override_attrs
|
47
|
+
user_attrs.reject do |key, value|
|
48
|
+
key.to_s.include?(OVERRIDE_KEY)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def flatten_hash(hash, parent_key = '', result_hash = {})
|
53
|
+
hash.each do |key, value|
|
54
|
+
new_key = parent_key.empty? ? key : "#{parent_key}_#{key}".to_sym
|
55
|
+
if value.is_a? Hash
|
56
|
+
flatten_hash(value, new_key, result_hash)
|
57
|
+
else
|
58
|
+
result_hash[new_key] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
result_hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge_hashes(hash1, hash2)
|
65
|
+
flat_hash1 = flatten_hash(hash1)
|
66
|
+
flat_hash2 = flatten_hash(hash2)
|
67
|
+
|
68
|
+
merged = flat_hash1.merge(flat_hash2) do |key, oldval, newval|
|
69
|
+
"#{oldval} #{newval}"
|
70
|
+
end
|
71
|
+
|
72
|
+
merged
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Avatar::Fallback < Base
|
5
|
+
def template(&)
|
6
|
+
span(**attrs, &)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def default_attrs
|
12
|
+
{
|
13
|
+
class: "flex h-full w-full items-center justify-center rounded-full bg-muted-background"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Avatar::Image < Base
|
5
|
+
def initialize(src:, alt: '', **attrs)
|
6
|
+
@src = src
|
7
|
+
@alt = alt
|
8
|
+
super(**attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def template
|
12
|
+
img(**attrs)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def default_attrs
|
18
|
+
{
|
19
|
+
loading: "lazy",
|
20
|
+
class: "aspect-square h-full w-full",
|
21
|
+
alt: @alt,
|
22
|
+
src: @src
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PhlexUI
|
4
|
+
class Avatar < Base
|
5
|
+
SIZES = {
|
6
|
+
xs: "h-4 w-4 text-[0.5rem]",
|
7
|
+
sm: "h-6 w-6 text-xs",
|
8
|
+
md: "h-10 w-10 text-base",
|
9
|
+
lg: "h-14 w-14 text-xl",
|
10
|
+
xl: "h-20 w-20 text-3xl"
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(size: :md, src: nil, alt: nil, initials: nil, **attrs)
|
14
|
+
@size = size
|
15
|
+
@src = src
|
16
|
+
@alt = alt
|
17
|
+
@initials = initials
|
18
|
+
@size_classes = SIZES[@size]
|
19
|
+
super(**attrs)
|
20
|
+
end
|
21
|
+
|
22
|
+
def template(&block)
|
23
|
+
if block_given?
|
24
|
+
span(**attrs, &block)
|
25
|
+
else
|
26
|
+
span(**attrs) do
|
27
|
+
render_image if @src
|
28
|
+
render_initials
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def render_image
|
36
|
+
render ::PhlexUI::Avatar::Image.new(src: @src, alt: @alt)
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_initials
|
40
|
+
render ::PhlexUI::Avatar::Fallback.new { @initials }
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_attrs
|
44
|
+
{
|
45
|
+
class: tokens("relative flex shrink-0 overflow-hidden rounded-full", @size_classes)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|