|
| 1 | +import { clsx } from 'clsx'; |
| 2 | +import { |
| 3 | + children, |
| 4 | + ComponentProps, |
| 5 | + createSignal, |
| 6 | + createUniqueId, |
| 7 | + FlowProps, |
| 8 | + JSX, |
| 9 | + mergeProps, |
| 10 | + Show, |
| 11 | + splitProps, |
| 12 | +} from 'solid-js'; |
| 13 | + |
| 14 | +// Tabs |
| 15 | +import { Tabs } from '@kobalte/core/tabs'; |
| 16 | + |
| 17 | +// TODO: Dropdown (use @kobalte) |
| 18 | +// Dropdown |
| 19 | +// import { |
| 20 | +// Menu, |
| 21 | +// MenuButton, |
| 22 | +// MenuItem, |
| 23 | +// MenuItems, |
| 24 | +// Switch, |
| 25 | +// Field, |
| 26 | +// Label, |
| 27 | +// type SwitchProps, |
| 28 | +// type MenuButtonProps, |
| 29 | +// type MenuItemProps, |
| 30 | +// type MenuItemsProps, |
| 31 | +// type MenuProps |
| 32 | +// } from '@headlessui/react' |
| 33 | + |
| 34 | +type TabValue = 'preview' | 'code'; |
| 35 | + |
| 36 | +// Switch |
| 37 | +import { Switch } from '@kobalte/core/switch'; |
| 38 | + |
| 39 | +// =========================================================================== |
| 40 | +// UI |
| 41 | +// =========================================================================== |
| 42 | + |
| 43 | +export type DemoProps = { |
| 44 | + ref?: HTMLDivElement; |
| 45 | + children: JSX.Element; |
| 46 | + class?: string; |
| 47 | + defaultValue?: TabValue; |
| 48 | + code?: JSX.Element; |
| 49 | + minHeight?: string; |
| 50 | + title?: JSX.Element; |
| 51 | +}; |
| 52 | + |
| 53 | +type Props = DemoProps & { onClick?: () => void }; |
| 54 | + |
| 55 | +function Demo(props: FlowProps<Props>) { |
| 56 | + const _props = mergeProps( |
| 57 | + { |
| 58 | + defaultValue: 'preview', |
| 59 | + minHeight: 'min-h-[20rem]', |
| 60 | + children: undefined, |
| 61 | + renderTitle: false, |
| 62 | + }, |
| 63 | + props, |
| 64 | + ); |
| 65 | + |
| 66 | + const [knowsToClick, setKnowsToClick] = createSignal(false); |
| 67 | + const [active, setActive] = createSignal(_props.defaultValue); |
| 68 | + |
| 69 | + const id = createUniqueId(); |
| 70 | + |
| 71 | + function handleClick() { |
| 72 | + setKnowsToClick(true); |
| 73 | + _props?.onClick?.(); |
| 74 | + } |
| 75 | + |
| 76 | + const handleMouseDown: JSX.EventHandler<HTMLElement, MouseEvent> = (event) => { |
| 77 | + // Prevent selection of text: |
| 78 | + // https://stackoverflow.com/a/43321596 |
| 79 | + if (event.detail > 1) { |
| 80 | + event.preventDefault(); |
| 81 | + } |
| 82 | + }; |
| 83 | + |
| 84 | + /** Prevent doublle-render when using it in <Show /> https://github.com/solidjs/solid/issues/2345#issuecomment-2427189199 */ |
| 85 | + const renderedTitle = children(() => _props.title); |
| 86 | + |
| 87 | + return ( |
| 88 | + <Tabs |
| 89 | + ref={_props.ref} |
| 90 | + class={clsx(active() === 'code' && 'dark', 'Demo text-primary not-prose relative isolate')} // reset text color if inside prose |
| 91 | + value={active()} |
| 92 | + onChange={(val) => setActive(val as TabValue)} |
| 93 | + // onValueChange={(val) => setActive(val as TabValue)} |
| 94 | + > |
| 95 | + <Show when={_props.code}> |
| 96 | + {/* <MotionConfig transition={{ layout: { type: 'spring', duration: 0.25, bounce: 0 } }}> */} |
| 97 | + <Tabs.List class="bg-zinc-150/90 absolute right-3 top-3 z-10 flex gap-1 rounded-full p-1 backdrop-blur-lg dark:bg-black/60"> |
| 98 | + <Tabs.Trigger |
| 99 | + value="preview" |
| 100 | + class={clsx( |
| 101 | + active() !== 'preview' && 'hover:transition-[color]', |
| 102 | + 'dark:text-muted hover:text-primary aria-selected:text-primary relative px-2 py-1 text-xs/4 font-medium text-zinc-600', |
| 103 | + )} |
| 104 | + > |
| 105 | + <Show when={active() === 'preview'}> |
| 106 | + {/* // Motion.div */} |
| 107 | + <div |
| 108 | + class="prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25" |
| 109 | + style={{ 'border-radius': '999' }} |
| 110 | + // layout |
| 111 | + // layoutId={`${id}active`} |
| 112 | + ></div> |
| 113 | + </Show> |
| 114 | + Preview |
| 115 | + </Tabs.Trigger> |
| 116 | + <Tabs.Trigger |
| 117 | + value="code" |
| 118 | + class={clsx( |
| 119 | + active() !== 'code' && 'hover:transition-[color]', |
| 120 | + 'dark:text-muted hover:text-primary aria-selected:text-primary relative px-2 py-1 text-xs/4 font-medium text-zinc-600', |
| 121 | + )} |
| 122 | + > |
| 123 | + <Show when={active() === _props.code}> |
| 124 | + {/* // Motion.div */} |
| 125 | + <div |
| 126 | + class="prefers-dark:!bg-white/15 absolute inset-0 -z-10 size-full bg-white shadow-sm dark:bg-white/25" |
| 127 | + style={{ 'border-radius': '999' }} |
| 128 | + // layout |
| 129 | + // layoutId={`${id}active`} |
| 130 | + ></div> |
| 131 | + </Show> |
| 132 | + Code |
| 133 | + </Tabs.Trigger> |
| 134 | + </Tabs.List> |
| 135 | + {/* </MotionConfig> */} |
| 136 | + </Show> |
| 137 | + <Tabs.Content |
| 138 | + value="preview" |
| 139 | + class={clsx( |
| 140 | + _props.class, |
| 141 | + 'border-faint relative rounded-lg border data-[state=inactive]:hidden', |
| 142 | + )} |
| 143 | + > |
| 144 | + <Show when={renderedTitle()}> |
| 145 | + <div class="absolute left-3 top-3">{renderedTitle()}</div> |
| 146 | + </Show> |
| 147 | + |
| 148 | + <div |
| 149 | + class={clsx(_props.minHeight, 'flex flex-col items-center justify-center p-5 pb-6')} |
| 150 | + onClick={_props?.onClick && handleClick} |
| 151 | + onMouseDown={_props?.onClick && handleMouseDown} |
| 152 | + > |
| 153 | + {_props.children} |
| 154 | + {_props?.onClick && ( |
| 155 | + <span |
| 156 | + class={clsx( |
| 157 | + 'absolute bottom-5 left-0 w-full text-center text-sm text-zinc-400 transition-opacity duration-200 ease-out', |
| 158 | + knowsToClick() && 'opacity-0', |
| 159 | + )} |
| 160 | + > |
| 161 | + Click anywhere to change numbers |
| 162 | + </span> |
| 163 | + )} |
| 164 | + </div> |
| 165 | + </Tabs.Content> |
| 166 | + <Show when={_props.code}> |
| 167 | + <Tabs.Content value="code">{_props?.code}</Tabs.Content>« |
| 168 | + </Show> |
| 169 | + </Tabs> |
| 170 | + ); |
| 171 | +} |
| 172 | + |
| 173 | +export default Demo; |
| 174 | + |
| 175 | +export function DemoTitle(props: JSX.IntrinsicElements['span'] & { children: string }) { |
| 176 | + const [_props, other] = splitProps(props, ['class', 'children']); |
| 177 | + |
| 178 | + return ( |
| 179 | + <span {...other} class={clsx(props.class, 'px-2 py-1.5 text-sm')}> |
| 180 | + {props.children} |
| 181 | + </span> |
| 182 | + ); |
| 183 | +} |
| 184 | + |
| 185 | +// export function DemoMenu(props: MenuProps) { |
| 186 | +// return <Menu {...props} /> |
| 187 | +// } |
| 188 | + |
| 189 | +// export function DemoMenuButton({ |
| 190 | +// children, |
| 191 | +// class, |
| 192 | +// ...props |
| 193 | +// }: MenuButtonProps & { children: JSX.Element }) { |
| 194 | +// return ( |
| 195 | +// <MenuButton |
| 196 | +// {...props} |
| 197 | +// class={clsx( |
| 198 | +// class, |
| 199 | +// 'group flex h-8 items-center rounded-full px-3 text-xs shadow-sm ring ring-black/[8%] dark:shadow-none dark:ring-white/10' |
| 200 | +// )} |
| 201 | +// > |
| 202 | +// {children} |
| 203 | +// <ChevronDown |
| 204 | +// class="spring-bounce-0 spring-duration-150 ml-1 size-4 shrink-0 group-data-[active]:rotate-180" |
| 205 | +// strokeWidth={2} |
| 206 | +// /> |
| 207 | +// </MenuButton> |
| 208 | +// ) |
| 209 | +// } |
| 210 | + |
| 211 | +// export function DemoMenuItems({ class, ...props }: MenuItemsProps) { |
| 212 | +// return ( |
| 213 | +// <MenuItems |
| 214 | +// {...props} |
| 215 | +// class={clsx( |
| 216 | +// class, |
| 217 | +// 'animate-pop-in dark:ring-white/12.5 absolute left-0 top-full mt-2 min-w-full origin-top-left rounded-xl bg-white/90 p-1.5 shadow-sm ring ring-inset ring-black/[8%] backdrop-blur-xl backdrop-saturate-[140%] dark:bg-zinc-950/90 dark:shadow-none' |
| 218 | +// )} |
| 219 | +// /> |
| 220 | +// ) |
| 221 | +// } |
| 222 | + |
| 223 | +// export function DemoMenuItem({ |
| 224 | +// class, |
| 225 | +// children, |
| 226 | +// ...props |
| 227 | +// }: MenuItemProps<'button'> & { children: JSX.Element }) { |
| 228 | +// return ( |
| 229 | +// <MenuItem |
| 230 | +// {...props} |
| 231 | +// as="button" |
| 232 | +// class={clsx( |
| 233 | +// class, |
| 234 | +// props.disabled ? 'pr-2' : 'pr-4', |
| 235 | +// 'dark:data-[focus]:bg-white/12.5 flex w-full items-center gap-2 rounded-lg py-2 pl-2 text-xs font-medium data-[disabled]:cursor-default data-[focus]:bg-black/5' |
| 236 | +// )} |
| 237 | +// > |
| 238 | +// {children} |
| 239 | +// {props.disabled && <Check class="ml-auto h-4 w-4" />} |
| 240 | +// </MenuItem> |
| 241 | +// ) |
| 242 | +// } |
| 243 | + |
| 244 | +export function DemoSwitch(props: ComponentProps<typeof Switch>) { |
| 245 | + const [_props, other] = splitProps(props, ['class', 'children']); |
| 246 | + |
| 247 | + return ( |
| 248 | + <Switch {...other} class="flex items-center gap-x-2"> |
| 249 | + <Switch.Control |
| 250 | + class={clsx( |
| 251 | + props.class, |
| 252 | + 'group relative flex h-6 w-11 cursor-pointer rounded-full bg-zinc-200 p-0.5 transition-colors duration-200 ease-in-out focus:outline-none data-[checked]:bg-zinc-950 data-[focus]:outline-2 data-[focus]:outline-blue-500 dark:bg-zinc-800 dark:data-[checked]:bg-zinc-50', |
| 253 | + )} |
| 254 | + > |
| 255 | + <Switch.Thumb class="spring-bounce-0 spring-duration-200 pointer-events-none inline-block size-5 rounded-full bg-white shadow-lg ring-0 transition-transform group-data-[checked]:translate-x-5 dark:bg-zinc-950" /> |
| 256 | + </Switch.Control> |
| 257 | + <Switch.Label class="text-xs">{props.children as any}</Switch.Label> |
| 258 | + </Switch> |
| 259 | + ); |
| 260 | +} |
0 commit comments