go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/generic_libs/components/dot_spinner.tsx (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import createCache from '@emotion/cache';
    16  import { CacheProvider, EmotionCache, keyframes } from '@emotion/react';
    17  import { Box, BoxProps, styled } from '@mui/material';
    18  import { LitElement } from 'lit';
    19  import { customElement } from 'lit/decorators.js';
    20  import { createRoot, Root } from 'react-dom/client';
    21  
    22  import { commonStyles } from '@/common/styles/stylesheets';
    23  
    24  const bounceEffect = keyframes`
    25    0%,
    26    80%,
    27    100% {
    28      transform: scale(0);
    29    }
    30    40% {
    31      transform: scale(1);
    32    }
    33  `;
    34  
    35  const Dot = styled(Box)<BoxProps>(() => ({
    36    width: '0.75em',
    37    height: '0.75em',
    38    borderRadius: '100%',
    39    backgroundColor: 'currentColor',
    40    display: 'inline-block',
    41    animation: `${bounceEffect} 1.4s infinite ease-in-out both`,
    42  }));
    43  
    44  /**
    45   * A simple 3-dots loading indicator.
    46   */
    47  export function DotSpinner() {
    48    return (
    49      <Box
    50        sx={{
    51          display: 'inline-block',
    52          textAlign: 'center',
    53          color: 'var(--active-text-color)',
    54        }}
    55      >
    56        <Dot
    57          sx={{
    58            animationDelay: '-0.32s',
    59          }}
    60        />
    61        <Dot
    62          sx={{
    63            animationDelay: '-0.16s',
    64          }}
    65        />
    66        <Dot />
    67      </Box>
    68    );
    69  }
    70  
    71  /**
    72   * A simple 3-dots loading indicator.
    73   */
    74  @customElement('milo-dot-spinner')
    75  export class DotSpinnerElement extends LitElement {
    76    private readonly cache: EmotionCache;
    77    private readonly parent: HTMLSpanElement;
    78    private readonly root: Root;
    79  
    80    constructor() {
    81      super();
    82      this.parent = document.createElement('span');
    83      const child = document.createElement('span');
    84      this.root = createRoot(child);
    85      this.parent.appendChild(child);
    86      this.cache = createCache({
    87        key: 'milo-dot-spinner',
    88        container: this.parent,
    89      });
    90    }
    91  
    92    protected render() {
    93      this.root.render(
    94        <CacheProvider value={this.cache}>
    95          <DotSpinner />
    96        </CacheProvider>,
    97      );
    98      return this.parent;
    99    }
   100  
   101    static styles = [commonStyles];
   102  }