Merge branch 'main' into jz-5-22-25-am

* main:
  Handle single items better
  Finishing touches
  Relative positioning for trays
  WIP
This commit is contained in:
Jason Zimdars
2025-05-22 14:27:00 -05:00
9 changed files with 177 additions and 126 deletions
-1
View File
@@ -41,7 +41,6 @@
--z-terminal: 20;
--z-tray: 21;
/* OKLCH colors: Fixed */
--lch-black: 0% 0 0;
--lch-white: 100% 0 0;
+6
View File
@@ -7,6 +7,12 @@
position: relative;
}
.card {
@media (prefers-color-scheme: dark) {
box-shadow: 0 0 0 1px var(--color-ink-lighter);
}
}
&:has(.card--notification) {
.notifications-list__empty-message {
display: none;
+14
View File
@@ -67,6 +67,10 @@
font: inherit;
}
button {
cursor: pointer;
}
/* Remove all animations and transitions for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
@@ -83,4 +87,14 @@
scroll-behavior: initial;
}
}
dialog {
border: 0;
padding: 0;
&:where(:focus-visible):focus,
&:where(:focus-visible):active {
outline: 0;
}
}
}
+118 -99
View File
@@ -3,102 +3,95 @@
}
@layer components {
.tray {
--tray-item-duration: 350ms;
--tray-item-height: 76px; /* FIXME: Magic number */
--tray-item-offset: var(--block-space);
--tray-bottom-padding: var(--block-space-half);
/* Container
/* ------------------------------------------------------------------------ */
background-color: transparent;
block-size: var(--footer-height);
border: 0;
display: flex;
flex-direction: column;
.tray {
--tray-duration: 350ms;
--tray-margin: var(--inline-space);
--tray-radius: 0.25rem;
--tray-timing-function: cubic-bezier(0.25, 1.25, 0.5, 1);
align-items: end;
block-size: calc(var(--footer-height) - var(--tray-margin) * 2);
display: grid;
inset-block: auto var(--tray-margin);
inline-size: var(--tray-size);
inset-block: auto 0;
justify-content: center;
padding: 0;
position: fixed;
z-index: var(--z-tray);
&:where(:focus-visible):focus,
&:where(:focus-visible):active {
outline: 0;
/* Make the dialog, expander, and actions inhabit the same space */
> * {
grid-column-start: 1;
grid-row-start: 1;
}
}
/* Dialog
/* ------------------------------------------------------------------------ */
.tray__dialog {
background-color: transparent;
display: flex;
flex-direction: column-reverse;
inline-size: auto;
inset: auto 0 0 0;
position: absolute;
transition: var(--tray-duration) var(--tray-timing-function);
transition-property: background-color, box-shadow, inset-block-end;
&:not([open]) {
block-size: var(--footer-height);
pointer-events: none;
}
:where(.tray__item) {
--tray-item-delay: calc((var(--tray-item-index) - 1) * 10ms);
--tray-item-y: calc((var(--tray-item-index) - 1) * (var(--tray-item-offset) * -1) - var(--tray-bottom-padding));
--tray-item-index: 1;
--tray-item-scale: calc(1 - var(--tray-item-index) / 30);
--tray-item-z: calc(6 - var(--tray-item-index));
&[open] {
border-radius: var(--tray-radius);
box-shadow:
0 0 0 1px var(--color-ink-lighter),
0 0 16px oklch(var(--lch-black) / 33%);
inset-block-end: calc(var(--footer-height) - var(--tray-margin));
overflow: hidden;
}
}
display: flex;
font-size: 10px;
inline-size: var(--tray-size);
inset: auto 0 0;
outline: 0;
pointer-events: none;
position: absolute; /* tmp fix; ideally should remove the position-relative class from tray__item */
scale: var(--tray-item-scale);
transform: translateY(var(--tray-item-y));
transform-origin: center;
transition:
border-radius var(--tray-item-duration),
box-shadow var(--tray-item-duration),
scale var(--tray-item-duration),
transform var(--tray-item-duration);
transition-delay: var(--tray-item-delay);
transition-timing-function: cubic-bezier(0.25, 1.25, 0.5, 1);
z-index: var(--tray-item-z);
container-type: inline-size;
/* Expander & Actions
/* ------------------------------------------------------------------------ */
&:nth-child(1) { --tray-item-index: 1; }
&:nth-child(2) { --tray-item-index: 2; }
&:nth-child(3) { --tray-item-index: 3; }
&:nth-child(4) { --tray-item-index: 4; }
&:nth-child(5) { --tray-item-index: 5; }
&:nth-child(6) { --tray-item-index: 6; }
.tray__expander {
--hover-size: 0;
--outline-size: 0;
.tray[open] & {
--tray-item-y: calc(-100% * var(--tray-item-index) + var(--tray-item-height) - var(--footer-height));
--tray-item-scale: 1;
align-self: stretch;
background: transparent;
border: 0;
margin-block: calc(-1 * var(--tray-margin));
z-index: calc(var(--z-tray) + 1);
pointer-events: unset;
}
.tray:has(.tray__item:only-child) & {
pointer-events: unset;
}
.tray__dialog[open] ~ &,
.tray__dialog:not(:has(.tray__item:not(.tray__item--overflow))) ~ & {
display: none;
}
}
.tray__actions {
--hover-size: 0;
align-self: center;
align-items: stretch;
border: 1px dashed color-mix(in srgb, var(--color-white) 40%, var(--color-ink));
border: 1px dashed oklch(var(--lch-white) / 50%);
border-radius: 0.5em;
color: var(--color-ink-inverted);
display: flex;
gap: var(--inline-space-half);
justify-content: space-between;
opacity: 0;
scale: calc(1 - 1 / 30);
transform: translateY(100%);
transition: opacity 0.2s ease-out, scale 0.2s ease-out, transform 0.2s ease-out;
transition: translate var(--tray-duration) var(--tray-timing-function);
translate: 0 var(--footer-height);
.tray[open] & {
.tray__dialog[open] ~ &,
.tray__dialog:not(:has(.tray__item:not(.tray__item--overflow))) ~ & {
opacity: 1;
scale: 1;
transform: translateY(0);
}
.tray:not(:has(.tray__item:not(.tray__item--overflow))) & {
opacity: 1;
scale: 1;
transform: none;
transition: none;
translate: 0 0;
}
/* Get the circle buttons to be full-height without magic numbers */
@@ -127,27 +120,53 @@
}
}
.tray__expander {
--hover-size: 0;
--outline-size: 0;
/* Item
/* ------------------------------------------------------------------------ */
background-color: transparent;
block-size: var(--tray-item-height);
border: 0;
cursor: pointer;
inset: auto 0 0 0;
padding: 0;
position: absolute;
.tray__item {
--tray-item-delay: calc((var(--tray-item-index) - 1) * 20ms);
--tray-item-height: 76px; /* FIXME: Magic number */
--tray-item-index: 1;
--tray-item-margin: calc(-1 * var(--tray-item-height) + var(--tray-item-offset));
--tray-item-offset: var(--block-space-half); /* The amount they overlap */
--tray-item-scale: calc(1 - (var(--tray-item-index) - 1) / 30);
--tray-item-z: calc(6 - var(--tray-item-index));
.tray[open] &,
.tray:not(:has(.tray__item)) &,
.tray:has(.tray__item:only-child) & {
display: none;
position: relative;
.tray__dialog & {
font-size: 10px;
transition: var(--tray-duration) var(--tray-timing-function);
transition-delay: var(--tray-item-delay);
transition-property: margin, scale;
z-index: var(--tray-item-z);
&:not(:first-child) {
scale: var(--tray-item-scale);
margin-block-end: var(--tray-item-margin);
}
&:nth-child(1) { --tray-item-index: 1; }
&:nth-child(2) { --tray-item-index: 2; }
&:nth-child(3) { --tray-item-index: 3; }
&:nth-child(4) { --tray-item-index: 4; }
&:nth-child(5) { --tray-item-index: 5; }
&:nth-child(6) { --tray-item-index: 6; }
&:nth-child(7) { --tray-item-index: 7; }
&:nth-child(8) { --tray-item-index: 8; }
&:nth-child(9) { --tray-item-index: 9; }
&:nth-child(10) { --tray-item-index: 10; }
}
.tray__dialog[open] & {
--tray-item-margin: 0;
--tray-item-scale: 1;
}
}
.tray__item--overflow {
--tray-item-index: 7 !important;
--tray-item-z: 1;
display: none;
opacity: 0;
@@ -160,16 +179,14 @@
justify-content: center;
}
.tray:has(.tray__item:nth-child(7)) & {
.tray__dialog:has(.tray__item:nth-child(7)) & {
display: flex;
}
.tray[open] & {
--tray-item-y: calc(-100% * var(--tray-item-index) + var(--tray-bottom-padding));
.tray__dialog[open] & {
--tray-item-scale: 1;
opacity: 1;
pointer-events: unset;
}
}
@@ -192,10 +209,7 @@
}
@media (prefers-color-scheme: dark) {
&,
[open] & {
box-shadow: 0 0 0 1px var(--color-ink-light);
}
box-shadow: 0 0 0 1px var(--color-ink-lighter);
}
}
@@ -220,15 +234,20 @@
/* ------------------------------------------------------------------------ */
.tray--pins {
inset-inline: var(--inline-space) auto;
inset-inline: var(--tray-margin) auto;
/* Don't expand if there's only one pin */
.tray__dialog:has(.tray__item:only-child) {
pointer-events: unset;
~ .tray__expander {
display: none;
}
}
.tray__item {
--tray-item-z: calc(10 - var(--tray-item-index));
&:nth-child(7) { --tray-item-index: 7; }
&:nth-child(8) { --tray-item-index: 8; }
&:nth-child(9) { --tray-item-index: 9; }
&:nth-child(10) { --tray-item-index: 10; }
&:nth-child(1n + 11) { display: none; }
}
@@ -287,7 +306,7 @@
/* ------------------------------------------------------------------------ */
.tray--notifications {
inset-inline: auto var(--inline-space);
inset-inline: auto var(--tray-margin);
.tray__item:nth-child(1n + 7) {
display: none;
@@ -6,12 +6,18 @@ export default class extends Controller {
modal: { type: Boolean, default: false }
}
connect() {
console.log("connect")
}
open() {
const modal = this.modalValue
if (modal) {
console.log(modal)
this.dialogTarget.showModal()
} else {
console.log("NOT FOUND")
this.dialogTarget.show()
}
}
@@ -1,6 +1,8 @@
<div class="card__background">
<%= image_tag card.image.presence || "", size: 120, data: { upload_preview_target: "image" } %>
</div>
<% if card.image.present? %>
<div class="card__background">
<%= image_tag card.image.presence || "", size: 120, data: { upload_preview_target: "image" } %>
</div>
<% end %>
<% if card.closed? %>
<div class="card__closed">
+1 -1
View File
@@ -48,9 +48,9 @@
<% if Current.user && !@hide_footer_frames %>
<div id="footer_frames" data-turbo-permanent="true" >
<%= render "notifications/tray" %>
<%= render "my/pins/tray" %>
<%= render "commands/terminal" %>
<%= render "notifications/tray" %>
</div>
<% end %>
</footer>
+10 -7
View File
@@ -1,13 +1,16 @@
<%= turbo_stream_from Current.user, :pins_tray %>
<%= tag.dialog id: "pin-tray", class: "tray tray--pins",
data: { controller: "dialog", turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %>
<%= turbo_frame_tag "pins", src: my_pins_path %>
<section class="tray tray--pins" data-controller="dialog">
<%= tag.dialog id: "pin-tray", class: "tray__dialog",
data: { turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %>
<%= turbo_frame_tag "pins", src: my_pins_path %>
<% end %>
<div class="tray__actions">
<button class="txt-uppercase btn btn--reversed borderless fill-transparent txt-x-small translucent center full-width" data-action="click->dialog#close">Pinned</button>
<button class="txt-uppercase btn btn--reversed borderless fill-transparent txt-x-small translucent center full-width" data-action="click->dialog#close">
Pinned
</button>
</div>
<button class="tray__expander" data-action="dialog#toggle" aria-label="Expand pins stack">
</button>
<% end %>
<button class="tray__expander" data-action="dialog#toggle" aria-label="Expand pins stack"></button>
</section>
+17 -15
View File
@@ -1,35 +1,37 @@
<%= turbo_stream_from Current.user, :notifications %>
<dialog class="tray tray--notifications"
data-controller="dialog" data-dialog-modal-value="false" data-dialog-target="dialog"
data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside">
<%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph" %>
<section class="tray tray--notifications" data-controller="dialog">
<dialog class="tray__dialog"
data-dialog-modal-value="false" data-dialog-target="dialog"
data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside">
<%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph" %>
<div class="tray__item tray__item--overflow">
<%= link_to notifications_path, class: "card", data: { action: "click->dialog#close" } do %>
+ More…
<% end %>
</div>
<button class="tray__expander" data-action="dialog#toggle" aria-label="Expand notifications stack"></button>
<div class="tray__item tray__item--overflow">
<%= link_to notifications_path, class: "card", data: { action: "click->dialog#close" } do %>
+ More…
<% end %>
</div>
</dialog>
<div class="tray__actions">
<%= button_to read_all_notifications_path,
class: "tray__notification-read-action btn btn--reversed fill-transparent borderless flex-item-justify-start shadow txt-xx-small",
class: "tray__notification-read-action btn btn--reversed fill-transparent borderless flex-item-justify-start txt-xx-small",
title: "Mark all as read",
data: { action: "click->dialog#close", turbo_frame: "notifications" } do %>
<%= icon_tag "check" %>
<% end %>
<%= link_to tag.span("Notifications"), notifications_path,
class: "btn btn--reversed borderless fill-transparent shadow center txt-uppercase txt-x-small full-width",
class: "btn btn--reversed borderless fill-transparent center txt-uppercase txt-x-small full-width",
data: { action: "click->dialog#close" } %>
<%= link_to settings_notifications_path,
class: "tray__notification-settings-action btn btn--reversed fill-transparent borderless flex-item-justify-start shadow txt-xx-small",
class: "tray__notification-settings-action btn btn--reversed fill-transparent borderless flex-item-justify-start txt-xx-small",
title: "Notification settings",
data: { action: "click->dialog#close" } do %>
<%= icon_tag "settings" %>
<% end %>
</div>
</dialog>
<button class="tray__expander" data-action="dialog#toggle" aria-label="Expand notifications stack"></button>
</section>