phlex_kit 0.2.1 → 0.2.2
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 +81 -6
- data/THIRD_PARTY_LICENSES +104 -0
- data/app/assets/stylesheets/phlex_kit/_tokens.css +72 -0
- data/app/assets/stylesheets/phlex_kit/phlex_kit.css +1 -1
- data/app/assets/stylesheets/phlex_kit/themes/claude.css +86 -0
- data/app/assets/stylesheets/phlex_kit/themes/neutral.css +95 -0
- data/app/assets/stylesheets/phlex_kit/themes/zinc.css +85 -0
- data/app/components/phlex_kit/accordion/accordion.css +13 -0
- data/app/components/phlex_kit/accordion/accordion.rb +11 -2
- data/app/components/phlex_kit/accordion/accordion_icon.rb +1 -3
- data/app/components/phlex_kit/accordion/accordion_item.rb +5 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog.css +45 -4
- data/app/components/phlex_kit/alert_dialog/alert_dialog_action.rb +3 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +6 -2
- data/app/components/phlex_kit/alert_dialog/alert_dialog_media.rb +14 -0
- data/app/components/phlex_kit/attachment/attachment.css +102 -2
- data/app/components/phlex_kit/attachment/attachment.rb +18 -6
- data/app/components/phlex_kit/attachment/attachment_action.rb +1 -6
- data/app/components/phlex_kit/attachment/attachment_group.rb +21 -0
- data/app/components/phlex_kit/attachment/attachment_media.rb +13 -4
- data/app/components/phlex_kit/attachment/attachment_trigger.rb +22 -0
- data/app/components/phlex_kit/breadcrumb/breadcrumb_ellipsis.rb +1 -5
- data/app/components/phlex_kit/breadcrumb/breadcrumb_separator.rb +1 -1
- data/app/components/phlex_kit/calendar/calendar_next.rb +1 -15
- data/app/components/phlex_kit/calendar/calendar_prev.rb +1 -15
- data/app/components/phlex_kit/card/card.css +50 -14
- data/app/components/phlex_kit/card/card.rb +6 -2
- data/app/components/phlex_kit/card/card_action.rb +14 -0
- data/app/components/phlex_kit/carousel/carousel_next.rb +1 -15
- data/app/components/phlex_kit/carousel/carousel_previous.rb +1 -15
- data/app/components/phlex_kit/codeblock/codeblock.css +1 -1
- data/app/components/phlex_kit/combobox/combobox_clear_button.rb +1 -15
- data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +1 -12
- data/app/components/phlex_kit/combobox/combobox_item_indicator.rb +1 -15
- data/app/components/phlex_kit/combobox/combobox_search_input.rb +1 -14
- data/app/components/phlex_kit/combobox/combobox_trigger.rb +1 -14
- data/app/components/phlex_kit/command/command.css +2 -2
- data/app/components/phlex_kit/command/command_input.rb +1 -13
- data/app/components/phlex_kit/context_menu/context_menu_item.rb +1 -3
- data/app/components/phlex_kit/data_table/data_table_column_toggle.rb +1 -13
- data/app/components/phlex_kit/data_table/data_table_expand_toggle.rb +1 -12
- data/app/components/phlex_kit/data_table/data_table_sort_head.rb +2 -28
- data/app/components/phlex_kit/dialog/dialog.css +8 -2
- data/app/components/phlex_kit/dialog/dialog_content.rb +1 -3
- data/app/components/phlex_kit/drawer/drawer.css +1 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +64 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_checkbox_item.rb +31 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_group.rb +11 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +5 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_group.rb +10 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_item.rb +31 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_shortcut.rb +10 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub.rb +11 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_content.rb +12 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +18 -0
- data/app/components/phlex_kit/icon/icon.rb +51 -0
- data/app/components/phlex_kit/marker/marker.css +25 -0
- data/app/components/phlex_kit/marker/marker.rb +18 -0
- data/app/components/phlex_kit/marker/marker_content.rb +7 -0
- data/app/components/phlex_kit/marker/marker_icon.rb +7 -0
- data/app/components/phlex_kit/native_select/native_select_icon.rb +1 -10
- data/app/components/phlex_kit/navigation_menu/navigation_menu_trigger.rb +1 -5
- data/app/components/phlex_kit/pagination/pagination_ellipsis.rb +1 -5
- data/app/components/phlex_kit/select/select_item.rb +1 -11
- data/app/components/phlex_kit/select/select_trigger.rb +1 -14
- data/app/components/phlex_kit/sheet/sheet.css +1 -1
- data/app/components/phlex_kit/sheet/sheet_content.rb +1 -3
- data/app/components/phlex_kit/sidebar/sidebar.css +129 -11
- data/app/components/phlex_kit/sidebar/sidebar.rb +19 -6
- data/app/components/phlex_kit/sidebar/sidebar_group_action.rb +14 -0
- data/app/components/phlex_kit/sidebar/sidebar_group_content.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_group_label.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_input.rb +14 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_action.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_badge.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_skeleton.rb +19 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub_button.rb +18 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub_item.rb +13 -0
- data/app/components/phlex_kit/sidebar/sidebar_separator.rb +14 -0
- data/app/components/phlex_kit/spinner/spinner.rb +5 -10
- data/app/components/phlex_kit/toast/toast_close.rb +1 -15
- data/app/components/phlex_kit/toast/toast_icon.rb +12 -36
- data/app/components/phlex_kit/typography/heading.rb +1 -1
- data/app/components/phlex_kit/typography/typography.css +7 -3
- data/app/javascript/phlex_kit/controllers/accordion_controller.js +17 -1
- data/app/javascript/phlex_kit/controllers/index.js +2 -0
- data/app/javascript/phlex_kit/controllers/scroll_fade_controller.js +31 -0
- data/lib/phlex_kit/configuration.rb +7 -0
- data/lib/phlex_kit/icons/lucide.rb +88 -0
- data/lib/phlex_kit/icons/phosphor.rb +88 -0
- data/lib/phlex_kit/icons/remix.rb +88 -0
- data/lib/phlex_kit/icons/tabler.rb +88 -0
- data/lib/phlex_kit/icons.rb +40 -0
- data/lib/phlex_kit/version.rb +1 -1
- data/lib/phlex_kit.rb +1 -0
- metadata +45 -8
- data/app/components/phlex_kit/stars/stars.css +0 -4
- data/app/components/phlex_kit/stars/stars.rb +0 -19
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
class AccordionItem < BaseComponent
|
|
3
|
-
def initialize(open: false, rotate_icon: 180, **attrs)
|
|
3
|
+
def initialize(open: false, disabled: false, rotate_icon: 180, **attrs)
|
|
4
4
|
@open = open
|
|
5
|
+
@disabled = disabled
|
|
5
6
|
@rotate_icon = rotate_icon
|
|
6
7
|
@attrs = attrs
|
|
7
8
|
end
|
|
8
9
|
def view_template(&)
|
|
9
|
-
|
|
10
|
+
data = { controller: "phlex-kit--accordion", phlex_kit__accordion_open_value: @open, phlex_kit__accordion_rotate_icon_value: @rotate_icon }
|
|
11
|
+
data[:disabled] = "" if @disabled
|
|
12
|
+
div(**mix({ class: "pk-accordion-item", data: data }, @attrs), &)
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
end
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
position: fixed;
|
|
9
9
|
inset: 0;
|
|
10
10
|
z-index: 50;
|
|
11
|
-
background:
|
|
12
|
-
backdrop-filter: blur(
|
|
11
|
+
background: rgb(0 0 0 / .5);
|
|
12
|
+
backdrop-filter: blur(8px);
|
|
13
13
|
}
|
|
14
14
|
.pk-alert-dialog-panel {
|
|
15
15
|
position: fixed;
|
|
@@ -28,10 +28,51 @@
|
|
|
28
28
|
border-radius: var(--pk-radius);
|
|
29
29
|
background: var(--pk-surface);
|
|
30
30
|
color: var(--pk-text);
|
|
31
|
-
|
|
31
|
+
--pk-dialog-spacing: 1.5rem;
|
|
32
|
+
padding: var(--pk-dialog-spacing);
|
|
32
33
|
box-shadow: 0 10px 40px rgba(0, 0, 0, .5);
|
|
33
34
|
}
|
|
35
|
+
.pk-alert-dialog-panel.sm { max-width: 24rem; --pk-dialog-spacing: 1rem; gap: .75rem; }
|
|
36
|
+
.pk-alert-dialog-panel.sm .pk-alert-dialog-title { font-size: 1rem; }
|
|
37
|
+
|
|
34
38
|
.pk-alert-dialog-header { display: flex; flex-direction: column; gap: .5rem; }
|
|
39
|
+
|
|
40
|
+
/* Media tile: a rounded-square icon chip; a header containing one becomes a
|
|
41
|
+
centered stack (shadcn's media layout). */
|
|
42
|
+
.pk-alert-dialog-media {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
flex: none;
|
|
47
|
+
width: 2.75rem;
|
|
48
|
+
height: 2.75rem;
|
|
49
|
+
border-radius: var(--pk-radius);
|
|
50
|
+
background: var(--pk-surface-2);
|
|
51
|
+
color: var(--pk-text);
|
|
52
|
+
margin-bottom: .25rem;
|
|
53
|
+
font-size: 1.25rem;
|
|
54
|
+
}
|
|
55
|
+
.pk-alert-dialog-media svg { width: 1.25rem; height: 1.25rem; }
|
|
56
|
+
.pk-alert-dialog-media img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
|
|
57
|
+
.pk-alert-dialog-header:has(.pk-alert-dialog-media) {
|
|
58
|
+
align-items: center;
|
|
59
|
+
text-align: center;
|
|
60
|
+
}
|
|
61
|
+
/* :sm dialogs pair the centered stack with a full-width 50/50 footer. */
|
|
62
|
+
.pk-alert-dialog-panel.sm .pk-alert-dialog-footer {
|
|
63
|
+
display: grid;
|
|
64
|
+
grid-template-columns: 1fr 1fr;
|
|
65
|
+
}
|
|
35
66
|
.pk-alert-dialog-title { margin: 0; font-size: 1.125rem; font-weight: 600; }
|
|
36
67
|
.pk-alert-dialog-description { margin: 0; font-size: .875rem; color: var(--pk-muted); }
|
|
37
|
-
|
|
68
|
+
/* Footer: shadcn's full-bleed muted band (escapes the panel padding). */
|
|
69
|
+
.pk-alert-dialog-footer {
|
|
70
|
+
display: flex;
|
|
71
|
+
justify-content: flex-end;
|
|
72
|
+
gap: .5rem;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
margin: 0 calc(-1 * var(--pk-dialog-spacing)) calc(-1 * var(--pk-dialog-spacing));
|
|
75
|
+
padding: var(--pk-dialog-spacing);
|
|
76
|
+
border-top: 1px solid var(--pk-border);
|
|
77
|
+
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
78
|
+
}
|
|
@@ -3,12 +3,13 @@ module PhlexKit
|
|
|
3
3
|
# non-GET destructive action, use a `button_to` in the footer instead, so it
|
|
4
4
|
# submits a real form.) See alert_dialog.rb.
|
|
5
5
|
class AlertDialogAction < BaseComponent
|
|
6
|
-
def initialize(**attrs)
|
|
6
|
+
def initialize(variant: :primary, **attrs)
|
|
7
|
+
@variant = variant
|
|
7
8
|
@attrs = attrs
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def view_template(&block)
|
|
11
|
-
render PhlexKit::Button.new(variant:
|
|
12
|
+
render PhlexKit::Button.new(variant: @variant, **@attrs, &block)
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -3,7 +3,11 @@ module PhlexKit
|
|
|
3
3
|
# cloned div carries its own `phlex-kit--alert-dialog` controller so Cancel
|
|
4
4
|
# (#dismiss) can remove it. Holds Header + Footer. See alert_dialog.rb.
|
|
5
5
|
class AlertDialogContent < BaseComponent
|
|
6
|
-
|
|
6
|
+
# size => panel modifier; :sm is shadcn's compact variant.
|
|
7
|
+
SIZES = { default: nil, sm: "sm" }.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(size: :default, **attrs)
|
|
10
|
+
@size = size.to_sym
|
|
7
11
|
@attrs = attrs
|
|
8
12
|
end
|
|
9
13
|
|
|
@@ -11,7 +15,7 @@ module PhlexKit
|
|
|
11
15
|
template(**mix({ data: { phlex_kit__alert_dialog_target: "content" } }, @attrs)) do
|
|
12
16
|
div(data: { controller: "phlex-kit--alert-dialog" }) do
|
|
13
17
|
div(class: "pk-alert-dialog-overlay", "aria-hidden": "true")
|
|
14
|
-
div(role: "alertdialog", "aria-modal": "true", class: "pk-alert-dialog-panel", &block)
|
|
18
|
+
div(role: "alertdialog", "aria-modal": "true", class: [ "pk-alert-dialog-panel", SIZES.fetch(@size) ].compact.join(" "), &block)
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Icon/image tile in an AlertDialogHeader — a muted circle above the title
|
|
3
|
+
# (beside the text column in size :sm dialogs). Ported from shadcn/ui's
|
|
4
|
+
# AlertDialogMedia (newer than ruby_ui's alert dialog). See alert_dialog.rb.
|
|
5
|
+
class AlertDialogMedia < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&)
|
|
11
|
+
div(**mix({ class: "pk-alert-dialog-media", aria: { hidden: "true" } }, @attrs), &)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* Co-located with attachment.rb — UI::Attachment, shadcn parity: bordered
|
|
2
|
-
file chip (icon/
|
|
2
|
+
file chip (icon/image media, name + meta, trailing actions), upload states
|
|
3
|
+
(uploading/processing shimmer the title; error goes destructive), three
|
|
4
|
+
sizes, vertical orientation, a full-card trigger behind the actions, and a
|
|
5
|
+
scrollable snapping group with an edge fade. */
|
|
3
6
|
.pk-attachment {
|
|
7
|
+
position: relative;
|
|
4
8
|
display: flex;
|
|
5
9
|
align-items: center;
|
|
6
10
|
gap: .5rem;
|
|
@@ -39,7 +43,14 @@
|
|
|
39
43
|
text-overflow: ellipsis;
|
|
40
44
|
}
|
|
41
45
|
.pk-attachment-description { color: var(--pk-muted); font-size: .75rem; }
|
|
42
|
-
.pk-attachment-actions {
|
|
46
|
+
.pk-attachment-actions {
|
|
47
|
+
position: relative; /* above the full-card trigger */
|
|
48
|
+
z-index: 2;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: .25rem;
|
|
52
|
+
flex: none;
|
|
53
|
+
}
|
|
43
54
|
.pk-attachment-action {
|
|
44
55
|
display: inline-flex;
|
|
45
56
|
align-items: center;
|
|
@@ -60,3 +71,92 @@
|
|
|
60
71
|
box-shadow: 0 0 0 2px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
61
72
|
}
|
|
62
73
|
.pk-attachment-action svg { width: .875rem; height: .875rem; }
|
|
74
|
+
|
|
75
|
+
/* Sizes. */
|
|
76
|
+
.pk-attachment.sm { padding: .375rem; gap: .375rem; }
|
|
77
|
+
.pk-attachment.sm .pk-attachment-media { width: 2rem; height: 2rem; }
|
|
78
|
+
.pk-attachment.xs { padding: .25rem .375rem; gap: .375rem; font-size: .8125rem; }
|
|
79
|
+
.pk-attachment.xs .pk-attachment-media { width: 1.5rem; height: 1.5rem; border-radius: calc(var(--pk-radius) - 4px); }
|
|
80
|
+
|
|
81
|
+
/* Vertical: media stacks above the content (image-preview cards). */
|
|
82
|
+
.pk-attachment.vertical {
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
align-items: stretch;
|
|
85
|
+
width: 10rem;
|
|
86
|
+
}
|
|
87
|
+
.pk-attachment.vertical .pk-attachment-media.image {
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: auto;
|
|
90
|
+
aspect-ratio: 4 / 3;
|
|
91
|
+
}
|
|
92
|
+
.pk-attachment.vertical .pk-attachment-actions {
|
|
93
|
+
position: absolute;
|
|
94
|
+
top: .75rem;
|
|
95
|
+
right: .75rem;
|
|
96
|
+
}
|
|
97
|
+
.pk-attachment.vertical .pk-attachment-action {
|
|
98
|
+
background: color-mix(in oklab, var(--pk-bg) 60%, transparent);
|
|
99
|
+
color: var(--pk-text);
|
|
100
|
+
backdrop-filter: blur(4px);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Upload states: uploading/processing shimmer the title (same recipe as
|
|
104
|
+
.pk-shimmer); error goes destructive. */
|
|
105
|
+
.pk-attachment[data-state="uploading"] .pk-attachment-title,
|
|
106
|
+
.pk-attachment[data-state="processing"] .pk-attachment-title {
|
|
107
|
+
--pk-shimmer-color: oklch(from currentColor calc(l + .25) c h);
|
|
108
|
+
background-image:
|
|
109
|
+
linear-gradient(110deg,
|
|
110
|
+
transparent calc(50% - (3ch + 40px) / 2),
|
|
111
|
+
var(--pk-shimmer-color) 50%,
|
|
112
|
+
transparent calc(50% + (3ch + 40px) / 2)),
|
|
113
|
+
linear-gradient(currentColor, currentColor);
|
|
114
|
+
background-size: 250% 100%, auto;
|
|
115
|
+
background-repeat: no-repeat;
|
|
116
|
+
-webkit-background-clip: text;
|
|
117
|
+
background-clip: text;
|
|
118
|
+
-webkit-text-fill-color: transparent;
|
|
119
|
+
animation: pk-shimmer 2s linear infinite;
|
|
120
|
+
}
|
|
121
|
+
.pk-attachment[data-state="error"] { border-color: color-mix(in oklab, var(--pk-red) 40%, transparent); }
|
|
122
|
+
.pk-attachment[data-state="error"] .pk-attachment-media { color: var(--pk-red); background: color-mix(in oklab, var(--pk-red) 12%, transparent); }
|
|
123
|
+
.pk-attachment[data-state="error"] .pk-attachment-description { color: var(--pk-red); }
|
|
124
|
+
|
|
125
|
+
/* Full-card trigger — behind the actions, focus ring on the card. */
|
|
126
|
+
.pk-attachment-trigger {
|
|
127
|
+
position: absolute;
|
|
128
|
+
inset: 0;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
border: 0;
|
|
131
|
+
border-radius: inherit;
|
|
132
|
+
background: transparent;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
padding: 0;
|
|
135
|
+
}
|
|
136
|
+
.pk-attachment-trigger:focus-visible { outline: none; }
|
|
137
|
+
.pk-attachment:has(.pk-attachment-trigger:hover) { background: color-mix(in oklab, var(--pk-surface-2) 40%, var(--pk-surface)); }
|
|
138
|
+
.pk-attachment:has(.pk-attachment-trigger:focus-visible) {
|
|
139
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Scrollable, snapping row with an edge fade (horizontal scroll-fade). */
|
|
143
|
+
.pk-attachment-group {
|
|
144
|
+
--pk-scroll-fade-size: 2rem;
|
|
145
|
+
display: flex;
|
|
146
|
+
gap: .5rem;
|
|
147
|
+
overflow-x: auto;
|
|
148
|
+
scroll-snap-type: x mandatory;
|
|
149
|
+
padding: 2px; /* room for card rings */
|
|
150
|
+
mask-image: linear-gradient(to right,
|
|
151
|
+
transparent 0, black var(--pk-scroll-fade-size),
|
|
152
|
+
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
153
|
+
}
|
|
154
|
+
.pk-attachment-group[data-at-start] {
|
|
155
|
+
mask-image: linear-gradient(to right,
|
|
156
|
+
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
157
|
+
}
|
|
158
|
+
.pk-attachment-group[data-at-end] {
|
|
159
|
+
mask-image: linear-gradient(to right, transparent 0, black var(--pk-scroll-fade-size));
|
|
160
|
+
}
|
|
161
|
+
.pk-attachment-group[data-at-start][data-at-end] { mask-image: none; }
|
|
162
|
+
.pk-attachment-group > .pk-attachment { flex: none; scroll-snap-align: start; }
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
# File chip, ported from shadcn/ui's Attachment (
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
2
|
+
# File chip, ported from shadcn/ui's Attachment: media (icon or image),
|
|
3
|
+
# name + metadata, optional actions, upload state and a full-card trigger.
|
|
4
|
+
# `state:` drives styling — :uploading/:processing shimmer the title, :error
|
|
5
|
+
# goes destructive; `size:` (:default/:sm/:xs) and `orientation:`
|
|
6
|
+
# (:horizontal/:vertical) shape the card. Lay several out in an
|
|
7
|
+
# AttachmentGroup (scrollable, snapping, edge-faded row).
|
|
6
8
|
# `.pk-attachment*` (attachment.css).
|
|
7
9
|
class Attachment < BaseComponent
|
|
8
|
-
|
|
10
|
+
STATES = { idle: "idle", uploading: "uploading", processing: "processing", error: "error", done: "done" }.freeze
|
|
11
|
+
SIZES = { default: nil, sm: "sm", xs: "xs" }.freeze
|
|
12
|
+
ORIENTATIONS = { horizontal: nil, vertical: "vertical" }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(state: :done, size: :default, orientation: :horizontal, **attrs)
|
|
15
|
+
@state = state.to_sym
|
|
16
|
+
@size = size.to_sym
|
|
17
|
+
@orientation = orientation.to_sym
|
|
9
18
|
@attrs = attrs
|
|
10
19
|
end
|
|
11
20
|
|
|
12
21
|
def view_template(&)
|
|
13
|
-
div(**mix({
|
|
22
|
+
div(**mix({
|
|
23
|
+
class: [ "pk-attachment", SIZES.fetch(@size), ORIENTATIONS.fetch(@orientation) ].compact.join(" "),
|
|
24
|
+
data: { state: STATES.fetch(@state) }
|
|
25
|
+
}, @attrs), &)
|
|
14
26
|
end
|
|
15
27
|
end
|
|
16
28
|
end
|
|
@@ -11,12 +11,7 @@ module PhlexKit
|
|
|
11
11
|
if block
|
|
12
12
|
yield
|
|
13
13
|
else
|
|
14
|
-
|
|
15
|
-
stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round",
|
|
16
|
-
"stroke-linejoin": "round", "aria-hidden": "true") do |s|
|
|
17
|
-
s.path(d: "M18 6 6 18")
|
|
18
|
-
s.path(d: "m6 6 12 12")
|
|
19
|
-
end
|
|
14
|
+
render Icon.new(:x, size: nil)
|
|
20
15
|
end
|
|
21
16
|
end
|
|
22
17
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Horizontally scrollable, snapping row of Attachments with an edge fade
|
|
3
|
+
# (the phlex-kit--scroll-fade controller in horizontal mode).
|
|
4
|
+
# See attachment.rb.
|
|
5
|
+
class AttachmentGroup < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&)
|
|
11
|
+
div(**mix({
|
|
12
|
+
class: "pk-attachment-group",
|
|
13
|
+
role: "group",
|
|
14
|
+
data: {
|
|
15
|
+
controller: "phlex-kit--scroll-fade",
|
|
16
|
+
phlex_kit__scroll_fade_axis_value: "x"
|
|
17
|
+
}
|
|
18
|
+
}, @attrs), &)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# Media slot of an Attachment: `variant: :icon` (default, a muted tile) or
|
|
3
|
+
# `:image` (render an <img> inside; fills the slot, full-width when the
|
|
4
|
+
# attachment is vertical). See attachment.rb.
|
|
4
5
|
class AttachmentMedia < BaseComponent
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
VARIANTS = { icon: nil, image: "image" }.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(variant: :icon, **attrs)
|
|
9
|
+
@variant = variant.to_sym
|
|
10
|
+
@attrs = attrs
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template(&)
|
|
14
|
+
div(**mix({ class: [ "pk-attachment-media", VARIANTS.fetch(@variant) ].compact.join(" ") }, @attrs), &)
|
|
15
|
+
end
|
|
7
16
|
end
|
|
8
17
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Full-card overlay that activates the attachment (a preview link or dialog
|
|
3
|
+
# trigger). Sits BEHIND AttachmentActions in the stacking order, so actions
|
|
4
|
+
# stay independently clickable. Give it an aria-label; pass `as: :a` +
|
|
5
|
+
# `href:` for a link. See attachment.rb.
|
|
6
|
+
class AttachmentTrigger < BaseComponent
|
|
7
|
+
def initialize(as: :button, href: nil, **attrs)
|
|
8
|
+
@as = as.to_sym
|
|
9
|
+
@href = href
|
|
10
|
+
@attrs = attrs
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
base = { class: "pk-attachment-trigger" }
|
|
15
|
+
if @as == :a
|
|
16
|
+
a(**mix(base.merge(href: @href), @attrs))
|
|
17
|
+
else
|
|
18
|
+
button(**mix(base.merge(type: :button), @attrs))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -3,11 +3,7 @@ module PhlexKit
|
|
|
3
3
|
def initialize(**attrs) = (@attrs = attrs)
|
|
4
4
|
def view_template
|
|
5
5
|
span(**mix({ class: "pk-breadcrumb-ellipsis", role: "presentation", aria: { hidden: true } }, @attrs)) do
|
|
6
|
-
|
|
7
|
-
s.circle(cx: "12", cy: "12", r: "1")
|
|
8
|
-
s.circle(cx: "19", cy: "12", r: "1")
|
|
9
|
-
s.circle(cx: "5", cy: "12", r: "1")
|
|
10
|
-
end
|
|
6
|
+
render Icon.new(:ellipsis, size: nil)
|
|
11
7
|
span(class: "pk-sr-only") { "More" }
|
|
12
8
|
end
|
|
13
9
|
end
|
|
@@ -3,7 +3,7 @@ module PhlexKit
|
|
|
3
3
|
def initialize(**attrs) = (@attrs = attrs)
|
|
4
4
|
def view_template(&block)
|
|
5
5
|
li(**mix({ class: "pk-breadcrumb-separator", role: "presentation", aria: { hidden: true } }, @attrs)) do
|
|
6
|
-
if block then yield else
|
|
6
|
+
if block then yield else render(Icon.new(:chevron_right, size: nil)) end
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -20,21 +20,7 @@ module PhlexKit
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def icon
|
|
23
|
-
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
width: "15",
|
|
26
|
-
height: "15",
|
|
27
|
-
viewbox: "0 0 15 15",
|
|
28
|
-
fill: "none",
|
|
29
|
-
"aria-hidden": "true"
|
|
30
|
-
) do |s|
|
|
31
|
-
s.path(
|
|
32
|
-
d: "M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z",
|
|
33
|
-
fill: "currentColor",
|
|
34
|
-
fill_rule: "evenodd",
|
|
35
|
-
clip_rule: "evenodd"
|
|
36
|
-
)
|
|
37
|
-
end
|
|
23
|
+
render Icon.new(:chevron_right, size: 15)
|
|
38
24
|
end
|
|
39
25
|
end
|
|
40
26
|
end
|
|
@@ -20,21 +20,7 @@ module PhlexKit
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def icon
|
|
23
|
-
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
width: "15",
|
|
26
|
-
height: "15",
|
|
27
|
-
viewbox: "0 0 15 15",
|
|
28
|
-
fill: "none",
|
|
29
|
-
"aria-hidden": "true"
|
|
30
|
-
) do |s|
|
|
31
|
-
s.path(
|
|
32
|
-
d: "M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z",
|
|
33
|
-
fill: "currentColor",
|
|
34
|
-
fill_rule: "evenodd",
|
|
35
|
-
clip_rule: "evenodd"
|
|
36
|
-
)
|
|
37
|
-
end
|
|
23
|
+
render Icon.new(:chevron_left, size: 15)
|
|
38
24
|
end
|
|
39
25
|
end
|
|
40
26
|
end
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
/* Co-located with card.rb — the UI::Card container kit (Card + Header / Title /
|
|
2
|
-
Description / Content / Footer)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
Description / Action / Content / Footer), matching shadcn/ui's current card:
|
|
3
|
+
every gap and inset derives from --pk-card-spacing (1rem; .75rem for
|
|
4
|
+
size :sm — or set it inline per card), the header becomes a 1fr/auto grid
|
|
5
|
+
when a CardAction is present, the footer is a full-bleed muted band, and a
|
|
6
|
+
leading <img> child runs edge-to-edge. Theme tokens come from the global
|
|
7
|
+
stylesheet. */
|
|
8
|
+
.pk-card {
|
|
9
|
+
--pk-card-spacing: 1rem;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--pk-card-spacing);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
border: 1px solid var(--pk-border);
|
|
15
|
+
border-radius: calc(var(--pk-radius) + 4px);
|
|
16
|
+
background: var(--pk-surface);
|
|
17
|
+
color: var(--pk-text);
|
|
18
|
+
padding-block: var(--pk-card-spacing);
|
|
19
|
+
font-size: .875rem;
|
|
20
|
+
}
|
|
21
|
+
.pk-card.sm { --pk-card-spacing: .75rem; }
|
|
22
|
+
/* the footer band and a leading image supply their own inset */
|
|
23
|
+
.pk-card:has(> .pk-card-footer:last-child) { padding-bottom: 0; }
|
|
24
|
+
.pk-card:has(> img:first-child) { padding-top: 0; }
|
|
25
|
+
.pk-card > img { width: 100%; object-fit: cover; }
|
|
26
|
+
|
|
27
|
+
.pk-card-header {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-auto-rows: min-content;
|
|
30
|
+
align-items: start;
|
|
31
|
+
gap: .25rem;
|
|
32
|
+
padding-inline: var(--pk-card-spacing);
|
|
33
|
+
}
|
|
34
|
+
.pk-card-header:has(.pk-card-action) { grid-template-columns: 1fr auto; }
|
|
35
|
+
.pk-card-title { margin: 0; font-weight: 600; line-height: 1.25; }
|
|
36
|
+
.pk-card-description { margin: 0; font-size: .875rem; color: var(--pk-muted); }
|
|
37
|
+
.pk-card-action {
|
|
38
|
+
grid-row: 1 / span 2;
|
|
39
|
+
grid-column: 2;
|
|
40
|
+
justify-self: end;
|
|
41
|
+
align-self: start;
|
|
42
|
+
}
|
|
43
|
+
.pk-card-content { padding-inline: var(--pk-card-spacing); }
|
|
44
|
+
.pk-card-footer {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: .5rem;
|
|
48
|
+
border-top: 1px solid var(--pk-border);
|
|
49
|
+
background: color-mix(in oklab, var(--pk-surface-2) 50%, transparent);
|
|
50
|
+
padding: var(--pk-card-spacing);
|
|
51
|
+
}
|
|
@@ -18,12 +18,16 @@ module PhlexKit
|
|
|
18
18
|
# render PhlexKit::CardFooter.new { render PhlexKit::Button.new { "Action" } }
|
|
19
19
|
# end
|
|
20
20
|
class Card < BaseComponent
|
|
21
|
-
|
|
21
|
+
# size => modifier class; :sm tightens --pk-card-spacing (shadcn's size prop).
|
|
22
|
+
SIZES = { default: nil, sm: "sm" }.freeze
|
|
23
|
+
|
|
24
|
+
def initialize(size: :default, **attrs)
|
|
25
|
+
@size = size.to_sym
|
|
22
26
|
@attrs = attrs
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
def view_template(&block)
|
|
26
|
-
div(**mix({ class: "pk-card" }, @attrs), &block)
|
|
30
|
+
div(**mix({ class: [ "pk-card", SIZES.fetch(@size) ].compact.join(" ") }, @attrs), &block)
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module PhlexKit
|
|
2
|
+
# Top-right slot of a CardHeader (a button, link or badge) — the header
|
|
3
|
+
# becomes a 1fr/auto grid when present. Ported from shadcn/ui's CardAction
|
|
4
|
+
# (newer than ruby_ui's card). See card.rb.
|
|
5
|
+
class CardAction < BaseComponent
|
|
6
|
+
def initialize(**attrs)
|
|
7
|
+
@attrs = attrs
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&)
|
|
11
|
+
div(**mix({ class: "pk-card-action" }, @attrs), &)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -19,21 +19,7 @@ module PhlexKit
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def icon
|
|
22
|
-
|
|
23
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
24
|
-
width: "24",
|
|
25
|
-
height: "24",
|
|
26
|
-
viewbox: "0 0 24 24",
|
|
27
|
-
fill: "none",
|
|
28
|
-
stroke: "currentColor",
|
|
29
|
-
"stroke-width": "2",
|
|
30
|
-
"stroke-linecap": "round",
|
|
31
|
-
"stroke-linejoin": "round",
|
|
32
|
-
"aria-hidden": "true"
|
|
33
|
-
) do |s|
|
|
34
|
-
s.path(d: "M5 12h14")
|
|
35
|
-
s.path(d: "m12 5 7 7-7 7")
|
|
36
|
-
end
|
|
22
|
+
render Icon.new(:arrow_right, size: 24)
|
|
37
23
|
end
|
|
38
24
|
end
|
|
39
25
|
end
|
|
@@ -20,21 +20,7 @@ module PhlexKit
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def icon
|
|
23
|
-
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
width: "24",
|
|
26
|
-
height: "24",
|
|
27
|
-
viewbox: "0 0 24 24",
|
|
28
|
-
fill: "none",
|
|
29
|
-
stroke: "currentColor",
|
|
30
|
-
"stroke-width": "2",
|
|
31
|
-
"stroke-linecap": "round",
|
|
32
|
-
"stroke-linejoin": "round",
|
|
33
|
-
"aria-hidden": "true"
|
|
34
|
-
) do |s|
|
|
35
|
-
s.path(d: "m12 19-7-7 7-7")
|
|
36
|
-
s.path(d: "M19 12H5")
|
|
37
|
-
end
|
|
23
|
+
render Icon.new(:arrow_left, size: 24)
|
|
38
24
|
end
|
|
39
25
|
end
|
|
40
26
|
end
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
background: var(--pk-surface-2); font-size: .875rem;
|
|
5
5
|
}
|
|
6
6
|
.pk-codeblock pre { margin: 0; padding: 1rem; }
|
|
7
|
-
.pk-codeblock code { font-family:
|
|
7
|
+
.pk-codeblock code { font-family: var(--pk-font-mono, ui-monospace, monospace); color: var(--pk-text); }
|
|
@@ -18,21 +18,7 @@ module PhlexKit
|
|
|
18
18
|
action: "phlex-kit--combobox#clearAll"
|
|
19
19
|
}
|
|
20
20
|
}, @attrs)) do
|
|
21
|
-
|
|
22
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
23
|
-
width: "24",
|
|
24
|
-
height: "24",
|
|
25
|
-
viewbox: "0 0 24 24",
|
|
26
|
-
fill: "none",
|
|
27
|
-
stroke: "currentColor",
|
|
28
|
-
"stroke-width": "2",
|
|
29
|
-
"stroke-linecap": "round",
|
|
30
|
-
"stroke-linejoin": "round",
|
|
31
|
-
"aria-hidden": "true"
|
|
32
|
-
) do |s|
|
|
33
|
-
s.path(d: "M18 6 6 18")
|
|
34
|
-
s.path(d: "m6 6 12 12")
|
|
35
|
-
end
|
|
21
|
+
render Icon.new(:x, size: 24)
|
|
36
22
|
end
|
|
37
23
|
end
|
|
38
24
|
end
|
|
@@ -51,18 +51,7 @@ module PhlexKit
|
|
|
51
51
|
|
|
52
52
|
def chevron_icon
|
|
53
53
|
span(class: "pk-combobox-input-trigger-icon") do
|
|
54
|
-
|
|
55
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
56
|
-
width: "24",
|
|
57
|
-
height: "24",
|
|
58
|
-
viewbox: "0 0 24 24",
|
|
59
|
-
fill: "none",
|
|
60
|
-
stroke: "currentColor",
|
|
61
|
-
"stroke-width": "2",
|
|
62
|
-
"stroke-linecap": "round",
|
|
63
|
-
"stroke-linejoin": "round",
|
|
64
|
-
"aria-hidden": "true"
|
|
65
|
-
) { |s| s.path(d: "m6 9 6 6 6-6") }
|
|
54
|
+
render Icon.new(:chevron_down, size: 24)
|
|
66
55
|
end
|
|
67
56
|
end
|
|
68
57
|
end
|