javascripthtmlcssflexboxoverflow

How to make one element shrink while two other elements stay the same


async function main() {
    const numStudents = Number(await new Modal('a\n\na\n\na\n\na\n\na\n\na', 'info', 'How many students do you have?', 'Cancel', 'Submit').response());
    const numGrades = Number(await new Modal('', 'info', 'How many grades does each student have?', 'Cancel', 'Submit').response());
}

class Modal {
    constructor(bodyText, type = 'info', headerText = null, footerText = 'Close', prompt = null, closeable = true) {
        this.type = type;
        if (headerText === null) {
            switch (this.type) {
                case 'success':
                    this.headerText = 'Success!';
                    break;
                case 'info':
                    this.headerText = 'Information';
                    break;
                case 'warning':
                    this.headerText = 'Warning!'
                    break;
                case 'error':
                    this.headerText = 'An error has occurred';
                    break;
                default:
                    this.headerText = 'Notification';
            }
        } else {
            this.headerText = headerText;
        }
        this.bodyText = bodyText;
        this.footerText = footerText;
        this.closeable = closeable;
        this.prompt = prompt;
        this.create();
        this.open();
    }

    create() {
        this.dialog = document.createElement('dialog');

        const header = document.createElement('header');
        header.classList.add(this.type, 'background');
        if (this.closeable) {
            const closeButton = document.createElement('button');
            closeButton.classList.add('close');
            closeButton.innerText = '×';
            header.appendChild(closeButton);
        }
        const headerText = document.createElement('h3');
        headerText.innerText = this.headerText;
        header.appendChild(headerText);
        this.dialog.appendChild(header);

        const form = document.createElement('form');
        form.method = 'dialog';

        const body = document.createElement('main');
        this.bodyText.split('\n\n').forEach((paragraph) => {
            const p = document.createElement('p');
            p.innerText = paragraph;
            body.appendChild(p);
        });
        if (this.prompt !== null) {
            this.input = document.createElement('input');
            this.input.placeholder = ' ';
            this.input.autofocus = true;
            const p = document.createElement('p');
            p.appendChild(this.input);
            body.appendChild(p);
        }
        form.appendChild(body);

        const footer = document.createElement('footer');
        footer.classList.add(this.type, 'text');
        const hiddenSubmitButton = document.createElement('button');
        hiddenSubmitButton.value = 'submit';
        hiddenSubmitButton.hidden = true;
        footer.appendChild(hiddenSubmitButton);
        const closeButton = document.createElement('button');
        closeButton.classList.add(this.type, 'text', 'animated');
        closeButton.innerText = this.footerText;
        footer.appendChild(closeButton);
        if (this.prompt === null) {
            closeButton.autofocus = true;
        } else {
            const submitButton = document.createElement('button');
            submitButton.classList.add(this.type, 'background', 'animated');
            submitButton.innerText = this.prompt;
            submitButton.value = 'submit';
            footer.appendChild(submitButton);
        }
        form.appendChild(footer);

        this.dialog.addEventListener('close', (event) => {
            this.close(event.target.returnValue);
        });
        this.dialog.appendChild(form);
        document.body.appendChild(this.dialog);
    }

    open() {
        this.dialog.showModal();
    }

    close(returnValue) {
        if (this.prompt !== null) {
            if (returnValue === '') {
                this.responseValue = null;
                if (this.rejectPromise !== undefined) {
                    this.rejectPromise('User canceled prompt');
                }
            } else {
                this.responseValue = this.input.value;
                if (this.rejectPromise !== undefined) {
                    this.resolvePromise(this.responseValue);
                }
            }
        }
    }

    response() {
        this.promise = new Promise((resolve, reject) => {
            if (this.responseValue !== undefined) {
                if (this.responseValue === null) {
                    reject('User canceled prompt')
                } else {
                    resolve(this.responseValue);
                }
            } else {
                this.resolvePromise = resolve;
                this.rejectPromise = reject;
            }
        });
        return this.promise;
    }
}

main();
:root {
    --error: #c00;
    --error-dark: #900;
    --error-light: #f00;
    --info: #36c;
    --info-dark: #039;
    --info-light: #69f;
    --muted: #ddd;
    --muted-dark: #888;
    --muted-light: #eee;
    --success: #0c0;
    --success-dark: #090;
    --success-light: #0f0;
    --warning: #cc0;
    --warning-dark: #990;
    --warning-light: #ff0;
}

body {
    font-family: Arial, Helvetica, sans-serif;
}

button {
    border: 2px solid;
    border-radius: 10px;
    cursor: pointer;
    margin: 1em 0.5em;
    padding: 10px 15px;
    transition: transform 1s;
}

button:active {
    transform: scale(90%);
}

button.animated {
    background-color: transparent;
    overflow: hidden;
    position: relative;
    transition: color 0.3s, border-color 0.3s, transform 0.2s;
    z-index: 1;
}

button.animated:hover {
    border-color: transparent;
}

button.animated::after {
    border: 0;
    border-radius: 50%;
    content: "";
    height: 200%;
    left: -50%;
    opacity: 0;
    position: absolute;
    transform: scale(0.1);
    transform-origin: center;
    transition: opacity 0.3s, transform 0.3s;
    top: -50%;
    width: 200%;
    z-index: -1;
}

button.animated:hover::after {
    opacity: 1;
    transform: scale(1);
}

input {
    border: 0;
    font: inherit;
    letter-spacing: normal;
    margin: 0;
    padding: 0;
}

input:focus, input:placeholder-shown {
    box-shadow: 0 2px 0 var(--muted);
    outline: none;
}

dialog {
    background-color: white;
    border: 0;
    border-radius: 10px;
    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
    opacity: 0;
    outline: none;
    padding: 0;
    transform: scale(0);
    transition: all 0.4s allow-discrete;
    width: 50%;
}

dialog:open {
    opacity: 1;
    transform: scale(1);
}

@starting-style {
    dialog:open {
        opacity: 0;
        transform: scale(0);
    }
}

dialog::backdrop {
    background-color: rgba(0, 0, 0, 0);
    transition: all 0.4s allow-discrete;
}

dialog:open::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
}

@starting-style {
    dialog:open::backdrop {
        background-color: rgba(0, 0, 0, 0);
    }
}

dialog header, dialog main, dialog footer {
    display: flow-root;
    padding: 0 1em;
    text-align: center;
}

dialog header {
    background-color: black;
    color: white;
}

dialog main, dialog footer {
    background-color: white;
    color: black;
}

dialog form {
    display: flex;
    flex-direction: column;
}

dialog header, dialog footer {
    flex: initial;
}

dialog main {
    flex: 1 1 auto;
    overflow-y: auto;
}

.close {
    aspect-ratio: 1 / 1;
    background-color: rgba(0, 0, 0, 0);
    border: 0;
    border-radius: 50%;
    box-sizing: border-box;
    color: var(--muted);
    font-size: 1.2em;
    font-weight: bold;
    height: 1.2em;
    margin: 0;
    padding: 0;
    position: absolute;
    right: 0.5rem;
    top: 0.5rem;
    user-select: none;
}

.close:hover,
.close:focus {
    background-color: rgba(255, 255, 255, 0.2);
    color: white;
}

.success.text, .info.text, .warning.text, .error.text, button.animated.success.background::after, button.animated.info.background::after, button.animated.warning.background::after, button.animated.error.background::after {
    background-color: white;
}

.success.background, .info.background, warning.background, error.background, button.animated.success.text:hover, button.animated.info.text:hover, button.animated.warning.text:hover, button.animated.error.text:hover {
    color: white;
}

button.animated.success.text, button.animated.info.text, button.animated.warning.text, button.animated.error.text {
    border-color: var(--muted);
}

.success.text, button.animated.success.background:hover {
    color: var(--success);
}

.success.background, button.animated.success.text::after {
    background-color: var(--success);
}

button.animated.success.text:hover, button.animated.success.background {
    border-color: var(--success);
} 

.info.text, button.animated.info.background:hover {
    color: var(--info);
}

.info.background, button.animated.info.text::after {
    background-color: var(--info);
}

button.animated.info.text:hover, button.animated.info.background {
    border-color: var(--info);
}

.warning.text, button.animated.warning.background:hover {
    color: var(--warning);
}

.warning.background, button.animated.warning.text::after {
    background-color: var(--warning);
}

button.animated.warning.text:hover, button.animated.warning.background {
    border-color: var(--warning);
}

.error.text, button.animated.error.background:hover {
    color: var(--error);
}

.error.background, button.animated.error.text::after {
    background-color: var(--error);
}

button.animated.error.text:hover, button.animated.error.background {
    border-color: var(--error);
}

I have a modal, as you can easily see in the link. When you resize the window until a scroll bar appears, you will see that the scroll bar appears for the entire modal. If you look closely, you can observe that the modal consists of a header (obviously), a main (the body text), and a footer (the button(s) at the bottom). I want the header and footer to act like a header and footer by only having the main portion have a scroll bar. How do I do this?


Solution

  • Since your grid is mainly a column layout , you may use grid instead flex and set the rows to auto and 1fr for the one supposed to fill the remaining space and scroll if needed.

    Below a snippet with a couple of grid and overflow rules to make main scroll:

    async function main() {
      const numStudents = Number(await new Modal('a\n\na\n\na\n\na\n\na\n\na', 'info', 'How many students do you have?', 'Cancel', 'Submit').response());
      const numGrades = Number(await new Modal('', 'info', 'How many grades does each student have?', 'Cancel', 'Submit').response());
    }
    
    class Modal {
      constructor(bodyText, type = 'info', headerText = null, footerText = 'Close', prompt = null, closeable = true) {
        this.type = type;
        if (headerText === null) {
          switch (this.type) {
            case 'success':
              this.headerText = 'Success!';
              break;
            case 'info':
              this.headerText = 'Information';
              break;
            case 'warning':
              this.headerText = 'Warning!'
              break;
            case 'error':
              this.headerText = 'An error has occurred';
              break;
            default:
              this.headerText = 'Notification';
          }
        } else {
          this.headerText = headerText;
        }
        this.bodyText = bodyText;
        this.footerText = footerText;
        this.closeable = closeable;
        this.prompt = prompt;
        this.create();
        this.open();
      }
    
      create() {
        this.dialog = document.createElement('dialog');
    
        const header = document.createElement('header');
        header.classList.add(this.type, 'background');
        if (this.closeable) {
          const closeButton = document.createElement('button');
          closeButton.classList.add('close');
          closeButton.innerText = '×';
          header.appendChild(closeButton);
        }
        const headerText = document.createElement('h3');
        headerText.innerText = this.headerText;
        header.appendChild(headerText);
        this.dialog.appendChild(header);
    
        const form = document.createElement('form');
        form.method = 'dialog';
    
        const body = document.createElement('main');
        this.bodyText.split('\n\n').forEach((paragraph) => {
          const p = document.createElement('p');
          p.innerText = paragraph;
          body.appendChild(p);
        });
        if (this.prompt !== null) {
          this.input = document.createElement('input');
          this.input.placeholder = ' ';
          this.input.autofocus = true;
          const p = document.createElement('p');
          p.appendChild(this.input);
          body.appendChild(p);
        }
        form.appendChild(body);
    
        const footer = document.createElement('footer');
        footer.classList.add(this.type, 'text');
        const hiddenSubmitButton = document.createElement('button');
        hiddenSubmitButton.value = 'submit';
        hiddenSubmitButton.hidden = true;
        footer.appendChild(hiddenSubmitButton);
        const closeButton = document.createElement('button');
        closeButton.classList.add(this.type, 'text', 'animated');
        closeButton.innerText = this.footerText;
        footer.appendChild(closeButton);
        if (this.prompt === null) {
          closeButton.autofocus = true;
        } else {
          const submitButton = document.createElement('button');
          submitButton.classList.add(this.type, 'background', 'animated');
          submitButton.innerText = this.prompt;
          submitButton.value = 'submit';
          footer.appendChild(submitButton);
        }
        form.appendChild(footer);
    
        this.dialog.addEventListener('close', (event) => {
          this.close(event.target.returnValue);
        });
        this.dialog.appendChild(form);
        document.body.appendChild(this.dialog);
      }
    
      open() {
        this.dialog.showModal();
      }
    
      close(returnValue) {
        if (this.prompt !== null) {
          if (returnValue === '') {
            this.responseValue = null;
            if (this.rejectPromise !== undefined) {
              this.rejectPromise('User canceled prompt');
            }
          } else {
            this.responseValue = this.input.value;
            if (this.rejectPromise !== undefined) {
              this.resolvePromise(this.responseValue);
            }
          }
        }
      }
    
      response() {
        this.promise = new Promise((resolve, reject) => {
          if (this.responseValue !== undefined) {
            if (this.responseValue === null) {
              reject('User canceled prompt')
            } else {
              resolve(this.responseValue);
            }
          } else {
            this.resolvePromise = resolve;
            this.rejectPromise = reject;
          }
        });
        return this.promise;
      }
    }
    
    main();
    :root {
      --error: #c00;
      --error-dark: #900;
      --error-light: #f00;
      --info: #36c;
      --info-dark: #039;
      --info-light: #69f;
      --muted: #ddd;
      --muted-dark: #888;
      --muted-light: #eee;
      --success: #0c0;
      --success-dark: #090;
      --success-light: #0f0;
      --warning: #cc0;
      --warning-dark: #990;
      --warning-light: #ff0;
    }
    
    body {
      font-family: Arial, Helvetica, sans-serif;
    }
    
    button {
      border: 2px solid;
      border-radius: 10px;
      cursor: pointer;
      margin: 1em 0.5em;
      padding: 10px 15px;
      transition: transform 1s;
    }
    
    button:active {
      transform: scale(90%);
    }
    
    button.animated {
      background-color: transparent;
      overflow: hidden;
      position: relative;
      transition: color 0.3s, border-color 0.3s, transform 0.2s;
      z-index: 1;
    }
    
    button.animated:hover {
      border-color: transparent;
    }
    
    button.animated::after {
      border: 0;
      border-radius: 50%;
      content: "";
      height: 200%;
      left: -50%;
      opacity: 0;
      position: absolute;
      transform: scale(0.1);
      transform-origin: center;
      transition: opacity 0.3s, transform 0.3s;
      top: -50%;
      width: 200%;
      z-index: -1;
    }
    
    button.animated:hover::after {
      opacity: 1;
      transform: scale(1);
    }
    
    input {
      border: 0;
      font: inherit;
      letter-spacing: normal;
      margin: 0;
      padding: 0;
    }
    
    input:focus,
    input:placeholder-shown {
      box-shadow: 0 2px 0 var(--muted);
      outline: none;
    }
    
    dialog {
      background-color: white;
      border: 0;
      border-radius: 10px;
      box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
      opacity: 0;
      outline: none;
      padding: 0;
      transform: scale(0);
      transition: all 0.4s allow-discrete;
      width: 50%;
      display: grid;
      grid-template-rows: auto 1fr;
      overflow: hidden;
      min-height:12em;
    }
    
    dialog:open {
      opacity: 1;
      transform: scale(1);
    }
    
    @starting-style {
      dialog:open {
        opacity: 0;
        transform: scale(0);
      }
    }
    
    dialog::backdrop {
      background-color: rgba(0, 0, 0, 0);
      transition: all 0.4s allow-discrete;
    }
    
    dialog:open::backdrop {
      background-color: rgba(0, 0, 0, 0.4);
    }
    
    @starting-style {
      dialog:open::backdrop {
        background-color: rgba(0, 0, 0, 0);
      }
    }
    
    dialog header,
    dialog main,
    dialog footer {
      display: flow-root;
      padding: 0 1em;
      text-align: center;
    }
    
    dialog header {
      background-color: black;
      color: white;
    }
    
    dialog main,
    dialog footer {
      background-color: white;
      color: black;
    }
    
    dialog form {
      display: grid;
      grid-template-rows: 1fr auto;
      overflow: hidden;
    }
    
    dialog header,
    dialog footer {
      flex: initial;
    }
    
    dialog main {
      overflow-y: auto;
    }
    
    .close {
      aspect-ratio: 1 / 1;
      background-color: rgba(0, 0, 0, 0);
      border: 0;
      border-radius: 50%;
      box-sizing: border-box;
      color: var(--muted);
      font-size: 1.2em;
      font-weight: bold;
      height: 1.2em;
      margin: 0;
      padding: 0;
      position: absolute;
      right: 0.5rem;
      top: 0.5rem;
      user-select: none;
    }
    
    .close:hover,
    .close:focus {
      background-color: rgba(255, 255, 255, 0.2);
      color: white;
    }
    
    .success.text,
    .info.text,
    .warning.text,
    .error.text,
    button.animated.success.background::after,
    button.animated.info.background::after,
    button.animated.warning.background::after,
    button.animated.error.background::after {
      background-color: white;
    }
    
    .success.background,
    .info.background,
    warning.background,
    error.background,
    button.animated.success.text:hover,
    button.animated.info.text:hover,
    button.animated.warning.text:hover,
    button.animated.error.text:hover {
      color: white;
    }
    
    button.animated.success.text,
    button.animated.info.text,
    button.animated.warning.text,
    button.animated.error.text {
      border-color: var(--muted);
    }
    
    .success.text,
    button.animated.success.background:hover {
      color: var(--success);
    }
    
    .success.background,
    button.animated.success.text::after {
      background-color: var(--success);
    }
    
    button.animated.success.text:hover,
    button.animated.success.background {
      border-color: var(--success);
    }
    
    .info.text,
    button.animated.info.background:hover {
      color: var(--info);
    }
    
    .info.background,
    button.animated.info.text::after {
      background-color: var(--info);
    }
    
    button.animated.info.text:hover,
    button.animated.info.background {
      border-color: var(--info);
    }
    
    .warning.text,
    button.animated.warning.background:hover {
      color: var(--warning);
    }
    
    .warning.background,
    button.animated.warning.text::after {
      background-color: var(--warning);
    }
    
    button.animated.warning.text:hover,
    button.animated.warning.background {
      border-color: var(--warning);
    }
    
    .error.text,
    button.animated.error.background:hover {
      color: var(--error);
    }
    
    .error.background,
    button.animated.error.text::after {
      background-color: var(--error);
    }