Soundorv0.13.0

Modal

A layer over the whole view that takes its input until it closes.

Modal is a layer over the whole view that takes its input until it closes. It is not a window: it is drawn in the plugin's one view, so it works the same in any host, and in the browser.

import { Modal, Pressable, Text, View } from '@soundor/react';
import { useState } from 'react';

function Button({ label, onPress }: { label: string; onPress: () => void }) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => ({
        paddingVertical: 8,
        paddingHorizontal: 14,
        borderRadius: 6,
        backgroundColor: pressed ? '#2a5fd0' : '#3a7bfd',
      })}
    >
      <Text style={{ color: 'white' }}>{label}</Text>
    </Pressable>
  );
}

// A modal takes the view's input and keyboard focus until it closes:
// Escape and a press on the backdrop ask it to.
export default function ConfirmDiscard() {
  const [confirming, setConfirming] = useState(false);
  const [discarded, setDiscarded] = useState(0);

  return (
    <View style={{ alignItems: 'center', gap: 12 }}>
      <Button label="Discard changes…" onPress={() => setConfirming(true)} />
      <Text style={{ color: '#8b8f98' }}>Discarded {discarded} times</Text>
      <Modal
        visible={confirming}
        onRequestClose={() => setConfirming(false)}
        dismissOnBackdropPress
        accessibilityLabel="Discard changes"
        backdropStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}
      >
        <View
          style={{
            margin: 'auto',
            gap: 12,
            padding: 16,
            borderRadius: 10,
            backgroundColor: '#24262c',
          }}
        >
          <Text style={{ color: '#e6e7ea', fontSize: 16 }}>
            Discard changes?
          </Text>
          <View style={{ flexDirection: 'row', gap: 8 }}>
            <Button label="Cancel" onPress={() => setConfirming(false)} />
            <Button
              label="Discard"
              onPress={() => {
                setDiscarded((count) => count + 1);
                setConfirming(false);
              }}
            />
          </View>
        </View>
      </Modal>
    </View>
  );
}
modal.tsxSource

It is a Portal plus:

  • A backdrop over the whole view that blocks the pointer from everything below, transparent unless backdropStyle says otherwise. Its children fill the view above it and stay interactive: place a dialog with margin: 'auto', say.
  • A trapped FocusScope with autoFocus and restoreFocus: focus goes to its first focusable child on open, Tab stays inside, and focus returns on close.
  • Close requests: Escape, and with dismissOnBackdropPress a press on the backdrop itself (not its content), call onRequestClose. The modal stays until visible is false: the caller decides.

Modals stack like portals: a modal opened from a modal is above it and owns focus until it closes, and a <Portal> opened from a modal (a dropdown, a tooltip) shows above that modal and belongs to its focus trap.

To assistive technology a modal is a modal dialog, named by accessibilityLabel. While it shows, everything else is hidden from it, except the portals opened from inside it; a modal opened from it takes over until it closes. With onRequestClose it offers escape (VoiceOver's scrub), which asks it to close as Escape does.

Props

Prop

Type

On this page