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.
*/