dcp-client
Version:
Core libraries for accessing DCP network
398 lines (370 loc) • 12.3 kB
CSS
/**
* @file dcp-modal.css
* Basic CSS rules for laying out the modals implemented in src/dcp-client/dom-tk/modals.js.
* This stylesheet should be used in conjunction with an overall appearance stylesheet from
* the base application to set key details like the text colours, fonts, form element
* appearance, etc.
* @author Wes Garland, wes@distributive.network
* @date Nov 2024
* @description
* A generic modal has the following layout (see also dialogTemplateHtml in the implementation):
* +----------------------------------------------+
* | CLOSE X |
* | TITLE |
* | |
* | ICON BODY |
* | |
* | BUTTONS PRIMARY_BUTTON |
* +----------------------------------------------+
*
* The overall border of the box is grey with a slight drop shadow. The DIALOG is positioned on the page
* via auto margins on all four sides; we pull it up to 10% for a more comfortable reading experience,
* and to permit dialogs with a lot of information before scrolling.
*/
DIALOG#dcp-modal {
position: fixed;
margin-top: 10%;
padding: 0;
max-width: 65%;
border-radius: 4px;
border: 1px solid rgb(138, 138, 138);
box-shadow: rgba(0, 0, 0, 0.4) 4px 4px 4px;
text-align: center;
/* Animate the dialog whenever it is rendered so that users can tell when they dismiss a dialog that
* gets immediately replaced by another identical dialog
*/
& {
transition: scale 0.3s, opacity 0.3s, margin-top 0.2s ease-out;
opacity: 1;
scale: 1;
@starting-style {
opacity: 0;
scale: 0;
margin-top: 50%;
}
}
}
/* Let the user resize alert boxes when the stack trace is visible */
DIALOG#dcp-modal.dcp-modal-alert {
&:has(#dcp-modal-stack[show-toggle="on"]) {
resize: both;
overflow-x: clip;
overflow-y: auto;
}
/* Undo the side-effects of the resize attrib when the stack trace gets hidden */
&:has(#dcp-modal-stack[show-toggle="off"]) {
width: revert ;
height: revert ;
}
}
/**
* Trick Chrome into ignoring max-width when the user uses the resize corner.
*/
DIALOG#dcp-modal[style*=width] {
max-width: unset;
}
DIALOG#dcp-modal:focus {
outline: none;
}
DIALOG#dcp-modal > * {
text-align: left;
}
/**
* The title section of the modal is optional; when it is not hidden, its background colour is
* primary-hue-main; we assume this is a colour that is dark enough for white text. The unset width and
* left/right padding ensure that titles wider than the body allow the overall box to grow without
* crowding the edges.
*/
DIALOG#dcp-modal H3#dcp-modal-title {
text-align: center;
overflow: hidden;
margin: 0;
padding: 1em;
background-color: var(--primary-hue-main);
color: white;
overflow: hidden;
}
/**
* The close X is a DIV with the X in the background image that is always at the top right corner. When
* the mouse hovers over it, we make the background darker. When the is not hidden, we invert the
* colour of the X, and re-invert the backdrop on hover to maintain the hover behaviour. The filters
* assume that
* - the X is black with a transparent background
* - white looks good on the title
* - black looks on the default background
*/
DIALOG#dcp-modal #dcp-modal-close-x {
background-image: url('/dcp-client/assets/x.svg');
background-size: contain;
height: 0.6em;
width: 0.6em;
padding: 3px;
margin: 2px;
border: none;
position: absolute;
top: 0;
right: 0;
}
DIALOG#dcp-modal #dcp-modal-close-x:hover {
backdrop-filter: brightness(0.8);
}
DIALOG#dcp-modal #dcp-modal-title:not(.dcp-modal-hidden) + #dcp-modal-close-x {
filter: invert(100%);
}
DIALOG#dcp-modal #dcp-modal-title:not(.dcp-modal-hidden) + #dcp-modal-close-x:hover {
backdrop-filter: brightness(0.8) invert(100%);
}
/**
* close-x is not used for modals immitating system modals
*/
DIALOG#dcp-modal.dcp-modal-alert > #dcp-modal-close-x,
DIALOG#dcp-modal.dcp-modal-confirm > #dcp-modal-close-x,
DIALOG#dcp-modal.dcp-modal-prompt > #dcp-modal-close-x {
display: none;
}
/**
* The content area exists so that the icon and the body can be vertically centered against each other.
*/
DIALOG#dcp-modal #dcp-modal-content-area {
display: flex;
align-items: center;
padding: 1em;
}
/**
* The modal icon, when present, displays to the left of all of the text.
*/
DIALOG#dcp-modal #dcp-modal-icon {
background-position: center;
background-size: contain;
background-repeat: no-repeat;
background-image: url('/dcp-client/assets/dcp-logo.png');
flex-shrink: 0;
height: 2em;
width: 2em;
padding: 0px;
margin: 0 0.75em 0 0;
border: none;
}
DIALOG#dcp-modal.dcp-modal-alert #dcp-modal-icon {
background-image: url('/dcp-client/assets/lucide/triangle-alert.svg');
}
DIALOG#dcp-modal.dcp-modal-alert:has(#dcp-modal-stack) #dcp-modal-icon {
background-image: url('/dcp-client/assets/lucide/bug.svg');
cursor: pointer;
}
DIALOG#dcp-modal.dcp-modal-confirm #dcp-modal-icon {
background-image: url('/dcp-client/assets/lucide/circle-alert.svg');
}
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-icon {
background-image: url('/dcp-client/assets/lucide/hard-drive-upload.svg');
}
/**
* Stack is hidden by default; clicking on the bug icon toggles the show-toggle attribute
*/
DIALOG#dcp-modal.dcp-modal-alert #dcp-modal-stack {
font-size: 0.7em;
display: none;
}
DIALOG#dcp-modal.dcp-modal-alert #dcp-modal-stack[show-toggle="on"] {
display: block;
}
DIALOG#dcp-modal #dcp-modal-body[mode="text"] {
white-space: pre-line;
}
/**
* Overall dialog is overflow: hidden, but we set things up here so that really large content will add
* scrollbars on the inside of the dialog, instead of becoming unmanageably large.
*/
DIALOG#dcp-modal #dcp-modal-body {
width: 100%;
overflow: auto;
max-height: 50vh;
}
DIALOG#dcp-modal.dcp-modal-prompt INPUT[type="number"],
DIALOG#dcp-modal.dcp-modal-prompt INPUT[type="password"],
DIALOG#dcp-modal.dcp-modal-prompt INPUT[type="text"] {
width: 100%;
}
/**
* The error section takes up no space unless it has content. It appears below the body.
*/
DIALOG#dcp-modal #dcp-modal-error {
text-align: center;
font-size: 0.75em;
padding: 0 0 1em 1em;
margin-top: -1em;
}
/**
* The button area holds the elements passed as buttons to cm.showModalDialog. They are floated to the
* right so that they appear in inverse order of declaration, putting the default primary button all the
* way to the right. A centered-buttons class has been provided to center the buttons in the modal. This
* class selector can be removed to make that the default behaviour. The centering happens by shrinking
* button-area to only the necessary size so that the top-level text-align of #dcp-modal can center it.
*/
DIALOG#dcp-modal #dcp-modal-button-area {
margin-left: 1em;
}
DIALOG#dcp-modal #dcp-modal-button-area > * {
margin-bottom: 1em;
margin-right: 1em;
}
DIALOG#dcp-modal #dcp-modal-button-area > * {
float: right;
position: relative;
}
DIALOG#dcp-modal.centered-buttons #dcp-modal-button-area {
display: inline-block;
}
/**
* Class used to hide elements, eg title, in a way that we can use for sibling combinators.
*/
DIALOG#dcp-modal .dcp-modal-hidden {
display: none;
}
/**
* Create the dialog opening behaviour. We animate a quick fade in/our of the backdrop to make things
* look smooth. Does not currently (Nov 2024) work in Firefox.
*/
DIALOG#dcp-modal[open]::backdrop {
animation: backdrop-fade 150ms ease forwards;
}
DIALOG#dcp-modal:not([open])::backdrop {
animation: backdrop-fade 50ms ease backwards;
animation-direction: reverse;
}
@keyframes backdrop-fade {
from { backdrop-filter: brightness(1.0); }
to { backdrop-filter: brightness(0.7); }
}
/**
* The upload modal has some unique positioning requirements. The icon is lifted from its normal
* position and centered within the modal. This allows it to render on top of the drop zone. The
* drop-zone uses a negative vertical position transformation to position the drop zone label. This
* means that the padding at the top of the body content needs to be become a margin to avoid cutting
* the top of the text off.
*/
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-icon {
position: absolute;
left: 50%;
transform: translate(-50%, 0.5em);
z-index: 1;
}
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-body {
text-align: center;
margin: 0;
padding: 0;
white-space: initial;
width: 100%;
z-index: 2;
}
DIALOG#dcp-modal.dcp-modal-upload:has(#dcp-modal-drop-target) #dcp-modal-content-area
{
padding-top: 0;
}
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-drop-target {
margin-top: 1em;
display: inline-block;
border: 2px dashed #bbb;
height: 6em;
width: calc(6em * 1.618);
}
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-drop-label {
display: inline-block;
position: relative;
transform: translateY(-0.9em);
color: grey;
background: white;
padding: 2px 4px 2px 4px;
font-size: 0.75em;
}
/** Control the behaviour of the upload modal when drag/dropping to give user feedback */
DIALOG#dcp-modal.dcp-modal-upload #dcp-modal-icon {
transition: all 100ms linear;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="over"] #dcp-modal-drop-target {
border-color: #777;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="over"] #dcp-modal-icon {
font-size: 1.5em;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="invalid"] {
cursor: not-allowed;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="over"] {
cursor: copy;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="invalid"] #dcp-modal-drop-target {
opacity: 0.35;
}
DIALOG#dcp-modal.dcp-modal-upload[drag-state="invalid"] #dcp-modal-icon {
font-size: 0;
opacity: 0;
}
/**
* Password dialogs can have one or two label+input divs, depending on need (enter vs create).
*/
DIALOG#dcp-modal:has(#dcp-modal-password-prompt) {
max-width: 45%;
INPUT[type] {
max-width: 50em;
}
LABEL {
line-height: 37px; /* remove when fixing dcp-style.css */
padding-right: 0.5em;
width: 25ch;
}
LABEL::after {
content: ":";
}
#dcp-modal-password-prompt,
#dcp-modal-password-confirm-prompt {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
& > DIV {
display: flex;
flex-grow: 1;
}
}
#dcp-modal-password-preamble {
&:not(:empty) {
padding-bottom: 0.5em;
}
}
}
/**
* Passphrase prompts with the showEye option are rendered as input[type="text"][obscured] instead of
* input type="password". A click handler is added to toggle when the obscured attribute on the sibling
* span element.
*/
DIALOG#dcp-modal DIV#dcp-modal-password-prompt,
DIALOG#dcp-modal DIV#dcp-modal-password-confirm-prompt {
/**
* INPUT[type="text"].with-eye is a password-like element with an eye icon that toggles password
* visibility. Visibility toggling happens by adding/removing the 'obscured' attribute.
*/
INPUT[type="text"].dcp-show-eye[obscured] {
-webkit-text-security: disc;
}
INPUT[type="text"].dcp-show-eye[obscured] + SPAN {
background-image: url("/dcp-client/assets/lucide/eye-off.svg");
}
INPUT[type="text"].dcp-show-eye + SPAN {
background-image: url("/dcp-client/assets/lucide/eye.svg");
}
/** Appearance of the eye itself */
INPUT.dcp-show-eye + SPAN {
display: inline-block;
position: relative;
background-repeat: no-repeat;
background-size: cover;
height: 16px;
width: 16px;
margin-left: -16px;
right: 8px;
transform: translateY(9px);
opacity: 50%;
cursor: pointer;
}
}