diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 35331adfb1..5ab813fef9 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -6,8 +6,9 @@ * Copyright Oxide Computer Company */ import * as Accordion from '@radix-ui/react-accordion' -import { useEffect, useState } from 'react' -import { useWatch } from 'react-hook-form' +import cn from 'classnames' +import { useEffect, useRef, useState } from 'react' +import { useWatch, type Control } from 'react-hook-form' import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import type { SetRequired } from 'type-fest' @@ -421,44 +422,7 @@ export function CreateInstanceForm() { Advanced - - - Networking - - - - - - - - Configuration - - - Data or scripts to be passed to cloud-init as{' '} - - user data - {' '} - - (examples) - {' '} - if the selected boot image supports it. Maximum size 32 KiB. - - } - name="userData" - label="User Data" - control={control} - /> - - - + Create instance @@ -468,20 +432,90 @@ export function CreateInstanceForm() { ) } -const AccordionHeader = ({ id, children }: { id: string; children: React.ReactNode }) => ( - - -
{children}
- -
-
-) +const AdvancedAccordion = ({ + control, + isSubmitting, +}: { + control: Control + isSubmitting: boolean +}) => { + // we track this state manually for the sole reason that we need to be able to + // tell, inside AccordionItem, when an accordion is opened so we can scroll its + // contents into view + const [openItems, setOpenItems] = useState([]) -const AccordionContent = ({ children }: { children: React.ReactNode }) => ( - -
{children}
-
-) + return ( + + + + + + + + } + name="userData" + label="User Data" + control={control} + /> + + + ) +} + +type AccordionItemProps = { + value: string + isOpen: boolean + label: string + children: React.ReactNode +} + +function AccordionItem({ value, label, children, isOpen }: AccordionItemProps) { + const contentRef = useRef(null) + + useEffect(() => { + if (isOpen && contentRef.current) { + contentRef.current.scrollIntoView({ behavior: 'smooth' }) + } + }, [isOpen]) + + return ( + + + +
{label}
+ +
+
+ + {children} + +
+ ) +} const SshKeysTable = () => { const keys = usePrefetchedApiQuery('currentUserSshKeyList', {}).data?.items || [] @@ -580,3 +614,16 @@ const PRESETS = [ { category: 'custom', id: 'custom', memory: 0, ncpus: 0 }, ] as const + +const UserDataDescription = () => ( + <> + Data or scripts to be passed to cloud-init as{' '} + + user data + {' '} + + (examples) + {' '} + if the selected boot image supports it. Maximum size 32 KiB. + +) diff --git a/libs/ui/styles/index.css b/libs/ui/styles/index.css index fdf05c7bb8..57c20eebcf 100644 --- a/libs/ui/styles/index.css +++ b/libs/ui/styles/index.css @@ -72,44 +72,6 @@ } } -@layer components { - @media screen and (min-width: 720px) { - .AccordionContent[data-state='open'] { - animation: accordionSlideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); - } - .AccordionContent[data-state='closed'] { - animation: accordionSlideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); - } - } - - @media screen and (prefers-reduced-motion) { - .AccordionContent[data-state='open'] { - animation-name: none; - } - .AccordionContent[data-state='closed'] { - animation-name: none; - } - } - - @keyframes accordionSlideDown { - from { - height: 0; - } - to { - height: var(--radix-accordion-content-height); - } - } - - @keyframes accordionSlideUp { - from { - height: var(--radix-accordion-content-height); - } - to { - height: 0; - } - } -} - /** * Remove focus ring for non-explicit scenarios. */