Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Progress component RFC #10

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

lukewarlow
Copy link

@lukewarlow lukewarlow commented Feb 27, 2025

I have an initial implementation to go along with this RFC. More thought is needed in terms of Custom Properties for colours etc.

const styles = `
@property --width {
    syntax: "<length>";
    inherits: false;
    initial-value: 0;
  }

  :host {
    display: inline-block;
    width: 200px;
    height: 10px;
  }

  :host::part(track) {
    width: 100%;
    height: 100%;
    border: thin solid light-dark(black, white);
    overflow: hidden;
    border-radius: 20px;
  }

  :host::part(fill) {
    width: var(--width, 0);
    height: 100%;
    background-color: light-dark(blue, lightblue);
  }

  :host(:state(indeterminate))::part(fill) {
    width: 20%;
    animation: progress 2.5s ease-in-out;
    animation-iteration-count: infinite;
    animation-fill-mode: both;
    position: relative;
    border-radius: inherit;
  }

  @keyframes progress {
    0% {
      left: 0;
    }
    50% {
      left: 80%;
    }
    100% {
      left: 0;
    }
  }
`;

const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(styles);

class OuiProgress extends HTMLElement {
  static formAssociated = true;
  static observedAttributes = ["value", "max"];

  #shadowRoot;
  #internals;

  #updateState() {
    this.#internals.ariaValueMin = 0;
    this.#internals.araiValueMax = this.max;
    this.#internals.states.clear();

    if (
      !this.attributes["value"] ||
      Number.isNaN(parseFloat(this.attributes["value"].value))
    ) {
      this.#internals.states.add("indeterminate");
    } else {
      this.#internals.ariaValueNow = this.value;
    }

    this.style.setProperty("--width", `${this.position * 100}%`);
  }

  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.#internals.role = "progressbar";
  }

  connectedCallback() {
    this.#shadowRoot = this.attachShadow({ mode: "closed" });
    this.#shadowRoot.adoptedStyleSheets = [stylesheet];
    const track = document.createElement('div');
    track.part = 'track';
    const fill = document.createElement('div');
    fill.part = 'fill';
    track.appendChild(fill);
    this.#shadowRoot.appendChild(track);
    this.#updateState();
  }

  attributeChangedCallback() {
    this.#updateState();
  }

  get value() {
    let value = parseFloat(this.attributes["value"]?.value);
    if (Number.isNaN(value) || value < 0) {
      value = 0;
    }

    return Math.min(value, this.max);
  }

  set value(value) {
    this.setAttribute("value", value?.toString());
  }

  get max() {
    let value = parseFloat(this.attributes["max"]?.value);
    if (Number.isNaN(value) || value <= 0) {
      value = 1;
    }

    return value;
  }

  set max(value) {
    this.setAttribute("max", value?.toString());
  }

  get position() {
    if (this.#internals.states.has("indeterminate")) {
      return -1;
    }

    return this.value / this.max;
  }
}

window.customElements.define("oui-progress", OuiProgress);

@lukewarlow lukewarlow marked this pull request as ready for review February 27, 2025 20:09
Comment on lines +62 to +64
| Slot | Description |
|------|-------------------------------------------------------------------|
| `default` | Fallback text to render if the component otherwise fails to load. |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to expose a slot for content in the filled in portion of the progress bar (e.g. a percentage number)?

Suggested change
| Slot | Description |
|------|-------------------------------------------------------------------|
| `default` | Fallback text to render if the component otherwise fails to load. |
| Slot | Description |
|------|-------------------------------------------------------------------|
| `default` | Fallback text to render if the component otherwise fails to load. |
| `fill` | Optional content for the filled in portion of the progress bar. |

@gfellerph
Copy link

Just wanted to see what the proposal looks like: https://codepen.io/tuelsch/pen/EaxoaJx?editors=1010

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants