示例

import { Button, List, Navigation, NavigationLink, NavigationStack, Script, Section, Text, Toggle, useState, VStack } from "scripting"

function SheetExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Showing a sheet</Text>}>
    <Button
      title={"Present"}
      action={() => setIsPresented(true)}
      sheet={{
        isPresented: isPresented,
        onChanged: setIsPresented,
        content: <VStack presentationDragIndicator={"visible"}>
          <Text font={"title"} padding={50}>Sheet content</Text>
          <Button title={"Dismiss"} action={() => setIsPresented(false)} />
        </VStack>
      }}
    />
  </Section>
}

function PopoverExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Showing a popover</Text>}>
    <Button
      title={"Show Popover"}
      action={() => setIsPresented(true)}
      popover={{
        isPresented: isPresented,
        onChanged: setIsPresented,
        presentationCompactAdaptation: "popover",
        content: <Text padding>Popover content</Text>,
        arrowEdge: "top",
      }}
    />
  </Section>
}

function FullScreenCoverExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Showing a full screen cover</Text>}>
    <Button
      title={"Present"}
      action={() => setIsPresented(true)}
      fullScreenCover={{
        isPresented: isPresented,
        onChanged: setIsPresented,
        content: <VStack
          onTapGesture={() => setIsPresented(false)}
          foregroundStyle={"white"}
          frame={{ maxHeight: "infinity", maxWidth: "infinity" }}
          background={"blue"}
          ignoresSafeArea
        >
          <Text>A full-screen modal view.</Text>
          <Text>Tap to dismiss</Text>
        </VStack>
      }}
    />
  </Section>
}

function ConfiguringSheetHeightExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Configuring sheet height</Text>}>
    <Button
      title={"Present"}
      action={() => setIsPresented(true)}
      sheet={{
        isPresented: isPresented,
        onChanged: setIsPresented,
        content: <VStack
          presentationDragIndicator={"visible"}
          presentationDetents={[200, "medium", "large"]}
        >
          <Text font={"title"} padding={50}>Drag the indicator to resize the sheet height.</Text>
          <Button title={"Dismiss"} action={() => setIsPresented(false)} />
        </VStack>
      }}
    />
  </Section>
}

function PresentAlertExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Present an alert view</Text>}>
    <Button
      title={"Present"}
      action={() => setIsPresented(true)}
      alert={{
        isPresented: isPresented,
        onChanged: setIsPresented,
        actions: <Button title={"OK"} action={() => { }} />,
        title: "Alert",
        message: <Text>Everything is OK</Text>
      }}
    />
  </Section>
}

function PresentConfirmationDialogExample() {
  const [isPresented, setIsPresented] = useState(false)

  return <Section header={<Text>Present a confirmation dialog</Text>}>
    <Button
      title={"Present"}
      action={() => setIsPresented(true)}
      confirmationDialog={{
        isPresented,
        onChanged: setIsPresented,
        title: "Do you want to delete this image?",
        actions: <Button
          title={"Delete"}
          role={"destructive"}
          action={() => {
            Dialog.alert({ message: "The image has been deleted." })
          }}
        />
      }}
    />
  </Section>
}

function MultiSheetArrayCase() {
  const [aPresented, setAPresented] = useState(false)
  const [bPresented, setBPresented] = useState(false)

  return <Section
    header={<Text>Case A · sheet[] array, two slots</Text>}
    footer={<Text font="caption">
      Tap each button to present its sheet. The Dismiss button inside
      should close only that sheet.
    </Text>}
  >
    <Button
      title="Present Sheet A"
      action={() => setAPresented(true)}
      sheet={[
        {
          isPresented: aPresented,
          onChanged: setAPresented,
          content: <VStack padding={30}>
            <Text font="title" foregroundStyle={"blue"}>Sheet A</Text>
            <Text>useDismiss should close Sheet A.</Text>
            <DismissButton expected="Sheet A" />
          </VStack>
        },
        {
          isPresented: bPresented,
          onChanged: setBPresented,
          content: <VStack padding={30}>
            <Text font="title" foregroundStyle={"orange"}>Sheet B</Text>
            <Text>useDismiss should close Sheet B.</Text>
            <DismissButton expected="Sheet B" />
          </VStack>
        }
      ]}
    />
    <Button
      title="Present Sheet B"
      action={() => setBPresented(true)}
    />
  </Section>
}

function MixedKindsCase() {
  const [sheetOn, setSheetOn] = useState(false)
  const [popoverOn, setPopoverOn] = useState(false)
  const [coverOn, setCoverOn] = useState(false)

  return <Section
    header={<Text>Case B · sheet + popover + fullScreenCover mixed</Text>}
    footer={<Text font="caption">
      Present each modal in turn. Dismiss inside each should close only
      that layer.
    </Text>}
  >
    <Button
      title="Present Sheet"
      action={() => setSheetOn(true)}
      sheet={{
        isPresented: sheetOn,
        onChanged: setSheetOn,
        content: <VStack padding={30}>
          <Text font="title">Sheet layer</Text>
          <DismissButton expected="Sheet layer" />
        </VStack>
      }}
      popover={{
        isPresented: popoverOn,
        onChanged: setPopoverOn,
        presentationCompactAdaptation: "popover",
        arrowEdge: "top",
        content: <VStack padding={20}>
          <Text>Popover layer</Text>
          <DismissButton expected="Popover layer" />
        </VStack>
      }}
      fullScreenCover={{
        isPresented: coverOn,
        onChanged: setCoverOn,
        content: <VStack
          frame={{ maxWidth: "infinity", maxHeight: "infinity" }}
          background={"black"}
          foregroundStyle={"white"}
          ignoresSafeArea
        >
          <Text font="title">FullScreenCover layer</Text>
          <DismissButton expected="FullScreenCover layer" />
        </VStack>
      }}
    />
    <Button
      title="Present Popover"
      action={() => setPopoverOn(true)}
    />
    <Button
      title="Present FullScreenCover"
      action={() => setCoverOn(true)}
    />
  </Section>
}

function ConditionalModifierShiftCase() {
  const [withPopover, setWithPopover] = useState(false)
  const [sheetOn, setSheetOn] = useState(false)
  const [popoverOn, setPopoverOn] = useState(false)

  return <Section
    header={<Text>Case C · conditional modifier (drift test)</Text>}
    footer={<Text font="caption">
      Toggle "Mount popover" a few times, then present Sheet. The Dismiss
      inside Sheet should always close Sheet — never any other layer.
    </Text>}
  >
    <Toggle
      title="Mount popover"
      value={withPopover}
      onChanged={setWithPopover}
    />
    <Button
      title="Present Sheet"
      action={() => setSheetOn(true)}
      sheet={{
        isPresented: sheetOn,
        onChanged: setSheetOn,
        content: <VStack padding={30}>
          <Text font="title">Sheet (Case C)</Text>
          <Text>Dismiss should close Sheet regardless of popover state.</Text>
          <DismissButton expected="Sheet (Case C)" />
        </VStack>
      }}
      popover={withPopover ? {
        isPresented: popoverOn,
        onChanged: setPopoverOn,
        presentationCompactAdaptation: "popover",
        arrowEdge: "top",
        content: <VStack padding={20}>
          <Text>Popover (Case C)</Text>
          <DismissButton expected="Popover (Case C)" />
        </VStack>
      } : undefined}
    />
    {withPopover && <Button
      title="Present Popover"
      action={() => setPopoverOn(true)}
    />}
  </Section>
}

function NavigationLinkWithSheetCase() {
  const [sheetOn, setSheetOn] = useState(false)

  return <Section
    header={<Text>Case D · NavigationLink + sheet on same component</Text>}
    footer={<Text font="caption">
      Push to Detail then Dismiss — should pop the detail, not close Sheet.
      Present Sheet then Dismiss — should close Sheet, not pop navigation.
    </Text>}
  >
    <NavigationLink
      destination={
        <VStack padding={20} navigationTitle="Detail D">
          <Text font="title">NavigationLink Detail</Text>
          <Text>useDismiss should pop this layer.</Text>
          <DismissButton expected="Detail D (pop)" />
        </VStack>
      }
    >
      <Text>Push to Detail</Text>
    </NavigationLink>
    <Button
      title="Present Sheet"
      action={() => setSheetOn(true)}
      sheet={{
        isPresented: sheetOn,
        onChanged: setSheetOn,
        content: <VStack padding={30}>
          <Text font="title">Sheet (Case D)</Text>
          <DismissButton expected="Sheet (Case D)" />
        </VStack>
      }}
    />
  </Section>
}

function DismissButton({ expected }: { expected: string }) {
  const dismiss = Navigation.useDismiss()
  return <Button
    title={`Dismiss (expected target: ${expected})`}
    action={() => dismiss()}
    padding
  />
}

function Example() {
  return <NavigationStack>
    <List
      navigationTitle={"Modal presentations"}
      navigationBarTitleDisplayMode={"inline"}
    >
      <SheetExample />
      <ConfiguringSheetHeightExample />
      <FullScreenCoverExample />
      <PopoverExample />
      <PresentAlertExample />
      <PresentConfirmationDialogExample />

      <MultiSheetArrayCase />
      <MixedKindsCase />
      <ConditionalModifierShiftCase />
      <NavigationLinkWithSheetCase />
    </List>
  </NavigationStack>
}

async function run() {
  await Navigation.present({
    element: <Example />
  })

  Script.exit()
}

run()