vitess.io/vitess@v0.16.2/web/vtadmin/src/components/routes/Settings.tsx (about) 1 import * as React from 'react'; 2 import { useDocumentTitle } from '../../hooks/useDocumentTitle'; 3 import { Theme, useTheme } from '../../hooks/useTheme'; 4 import { Icon, Icons } from '../Icon'; 5 import { Select } from '../inputs/Select'; 6 import { Intent } from '../intent'; 7 import { ContentContainer } from '../layout/ContentContainer'; 8 import { warn, danger, success, info } from '../Snackbar'; 9 import { Tab } from '../tabs/Tab'; 10 import { TabContainer } from '../tabs/TabContainer'; 11 import { TextInput } from '../TextInput'; 12 import Toggle from '../toggle/Toggle'; 13 import { Tooltip } from '../tooltip/Tooltip'; 14 import { env } from '../../util/env'; 15 import style from './Settings.module.scss'; 16 17 /* eslint-disable jsx-a11y/anchor-is-valid */ 18 export const Settings = () => { 19 useDocumentTitle('Debug'); 20 const [theme, setTheme] = useTheme(); 21 const [formData, setFormData] = React.useState<{ [key: string]: any }>({}); 22 const [enabled, setEnabled] = React.useState(false); 23 24 return ( 25 <ContentContainer> 26 <div className={style.container}> 27 <h1 className="mt-8">Settings</h1> 28 29 <h2 className="mt-12 mb-8">Environment variables</h2> 30 <pre>{JSON.stringify(env(), null, 2)}</pre> 31 32 {process.env.NODE_ENV !== 'production' && ( 33 <> 34 <h2 className="mt-12 mb-8">Style Guide</h2> 35 36 <section> 37 <h3 className="mt-12 mb-8">Theme</h3> 38 <div> 39 {Object.values(Theme).map((t) => ( 40 <div key={t}> 41 <label> 42 <input 43 checked={theme === t} 44 name="theme" 45 onChange={() => setTheme(t)} 46 type="radio" 47 value={t} 48 /> 49 {t} 50 </label> 51 </div> 52 ))} 53 </div> 54 </section> 55 56 <section> 57 <h3 className="mt-12 mb-8">Colours</h3> 58 {[ 59 [style.danger, style.danger50, style.danger200], 60 [style.success, style.success50, style.success200], 61 [style.warning, style.warning50, style.warning200], 62 [style.vtblue, style.vtblue50, style.vtblue200], 63 [style.vtblueDark, style.vtblueDark50, style.vtblueDark200], 64 [ 65 style.gray75, 66 style.gray100, 67 style.gray200, 68 style.gray400, 69 style.gray600, 70 style.gray800, 71 style.gray900, 72 ], 73 ].map((colors, idx) => { 74 return ( 75 <div className="flex my-8" key={idx}> 76 {colors.map((c) => ( 77 <div className={`${c} mr-4 font-semibold text-sm`} key={c}> 78 <div className="w-40 h-16 rounded" /> 79 </div> 80 ))} 81 </div> 82 ); 83 })} 84 </section> 85 86 <section> 87 <h3 className="mt-12 mb-8">Typography</h3> 88 89 <div className="my-16"> 90 <p className="text-sm">The quick brown fox ...</p> 91 <p className="text-base">The quick brown fox ...</p> 92 <p className="text-lg">The quick brown fox ...</p> 93 <p className="text-xl">The quick brown fox ...</p> 94 <p className="text-2xl">The quick brown fox ...</p> 95 <p className="text-3xl">The quick brown fox ...</p> 96 </div> 97 98 <div className="my-16"> 99 <p className="text-sm font-mono">The quick brown fox ...</p> 100 <p className="text-base font-mono">The quick brown fox ...</p> 101 <p className="text-lg font-mono">The quick brown fox ...</p> 102 <p className="text-xl font-mono">The quick brown fox ...</p> 103 <p className="text-2xl font-mono">The quick brown fox ...</p> 104 <p className="text-3xl font-mono">The quick brown fox ...</p> 105 </div> 106 107 <div className="max-w-prose my-16"> 108 <p> 109 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi in facilisis libero, 110 eget congue lorem. Nulla non ligula eget erat aliquam lacinia a eget felis. Nulla 111 turpis sapien, ultricies sit amet suscipit quis, auctor id risus. Donec pellentesque 112 tellus metus, in eleifend lacus euismod eget. Vestibulum vehicula ut metus id 113 vestibulum. Nam vulputate sapien sit amet tempor efficitur. Donec dictum tellus nec 114 leo fringilla, eu tempor neque posuere. Integer porta, velit quis interdum 115 ultricies, quam enim suscipit eros, quis ornare metus ante vitae est. Sed quis 116 dignissim justo, at ultrices urna. Suspendisse gravida, tortor sed semper tristique, 117 erat metus vulputate augue, quis tempor mi nisi vitae magna. Donec auctor fermentum 118 magna, ut feugiat odio tincidunt sit amet. Donec accumsan lorem mi, ut placerat ex 119 lacinia vel. Nullam ut magna feugiat, ornare nibh vel, tincidunt nisi. Fusce 120 tincidunt malesuada posuere. 121 </p> 122 123 <p> 124 Cras elementum lacinia tristique. Vestibulum nec sem sit amet velit lobortis 125 accumsan ut eget lacus. Nulla eros ipsum, pellentesque sed vehicula id, pulvinar ut 126 dolor. Proin facilisis ligula vel faucibus iaculis. Donec venenatis massa lorem, sed 127 tempus libero vulputate nec. Vestibulum semper nibh id tortor dapibus, a 128 pellentesque libero porttitor. Pellentesque id elit nulla. Duis congue hendrerit 129 rhoncus. Vivamus accumsan tincidunt augue in sollicitudin. Cras sed augue eget elit 130 semper interdum tristique at ligula. Aliquam vel pretium sem. Donec egestas nisi 131 blandit congue pretium. 132 </p> 133 134 <p> 135 Duis vulputate blandit ante nec bibendum. Duis ipsum augue, tempus et viverra et, 136 ultricies ac sapien. Nullam vel laoreet turpis, in convallis eros. Suspendisse sit 137 amet magna turpis. Curabitur mi risus, facilisis vel fringilla ut, facilisis nec 138 tortor. Mauris rutrum vehicula justo ac dapibus. Sed aliquam, eros non bibendum 139 sodales, mauris est iaculis dui, ut tristique nibh sapien ac sapien. Lorem ipsum 140 dolor sit amet, consectetur adipiscing elit. 141 </p> 142 </div> 143 </section> 144 145 <section> 146 <h3 className="mt-12 mb-8">Tabs</h3> 147 148 <TabContainer className={style.tabContainer} size="small"> 149 <Tab text="Small Tab" to="/debug/small/1" /> 150 <Tab text="Small Tab" to="/debug/small/2" count={1} /> 151 <Tab text="Small Tab" to="/debug/small/3" status="danger" /> 152 </TabContainer> 153 154 <TabContainer className={style.tabContainer} size="medium"> 155 <Tab text="Medium Tab" to="/debug/medium/1" /> 156 <Tab text="Medium Tab" to="/debug/medium/2" status="success" count={1000} /> 157 <Tab text="Medium Tab" to="/debug/medium/3" /> 158 </TabContainer> 159 160 <TabContainer className={style.tabContainer} size="large"> 161 <Tab text="Large Tab" to="/debug/large/1" /> 162 <Tab text="Large Tab" to="/debug/large/2" status="primary" /> 163 <Tab text="Large Tab" to="/debug/large/3" count={10000} /> 164 </TabContainer> 165 </section> 166 167 <section> 168 <h3 className="mt-12 mb-8">Icons</h3> 169 <div className={style.iconContainer}> 170 {Object.values(Icons).map((i) => ( 171 <Tooltip text={i}> 172 <Icon className={style.icon} icon={i} key={i} tabIndex={0} /> 173 </Tooltip> 174 ))} 175 </div> 176 </section> 177 <section> 178 <h3 className="mt-12 mb-8">Toggle</h3> 179 <Toggle enabled={enabled} onChange={() => setEnabled(!enabled)} /> 180 </section> 181 <section> 182 <h3 className="mt-12 mb-8">Select</h3> 183 <div className={style.dropdownContainer}> 184 <div className={style.dropdownRow}> 185 <Select 186 itemToString={(fruit) => fruit?.name || ''} 187 items={FRUITS} 188 label="Fruits" 189 onChange={(fruit) => setFormData({ ...formData, selectFruitDefault: fruit })} 190 placeholder="Choose a fruit" 191 renderItem={(fruit) => `${fruit.emoji} ${fruit.name}`} 192 selectedItem={formData.selectFruitDefault || null} 193 /> 194 <Select 195 items={FRUIT_NAMES} 196 label="Fruit names" 197 onChange={(fruitName) => 198 setFormData({ ...formData, selectFruitNameDefault: fruitName }) 199 } 200 placeholder="Choose a fruit name" 201 selectedItem={formData.selectFruitNameDefault || null} 202 /> 203 <Select 204 disabled 205 itemToString={(fruit) => fruit?.name || ''} 206 items={FRUITS} 207 label="Fruits" 208 onChange={(fruit) => setFormData({ ...formData, selectFruitDefault: fruit })} 209 placeholder="Choose a fruit" 210 renderItem={(fruit) => `${fruit.emoji} ${fruit.name}`} 211 selectedItem={formData.selectFruitDefault || null} 212 /> 213 <Select 214 items={[]} 215 emptyPlaceholder="No fruits :(" 216 label="Empty Fruits" 217 onChange={(fruit) => setFormData({ ...formData, selectFruitDefault: fruit })} 218 placeholder="Choose a fruit" 219 renderItem={(fruit) => `${fruit.emoji} ${fruit.name}`} 220 selectedItem={formData.selectFruitDefault || null} 221 /> 222 </div> 223 <div className={style.dropdownRow}> 224 <Select 225 itemToString={(fruit) => fruit?.name || ''} 226 items={FRUITS} 227 label="Fruits" 228 onChange={(fruit) => setFormData({ ...formData, selectFruitLarge: fruit })} 229 placeholder="Choose a fruit" 230 renderItem={(fruit) => `${fruit.emoji} ${fruit.name}`} 231 selectedItem={formData.selectFruitLarge || null} 232 size="large" 233 /> 234 <Select 235 items={FRUIT_NAMES} 236 label="Fruit names" 237 onChange={(fruitName) => 238 setFormData({ ...formData, selectFruitNameLarge: fruitName }) 239 } 240 placeholder="Choose a fruit name" 241 size="large" 242 selectedItem={formData.selectFruitNameLarge || null} 243 /> 244 <Select 245 disabled 246 itemToString={(fruit) => fruit?.name || ''} 247 items={FRUITS} 248 label="Fruits" 249 onChange={(fruit) => setFormData({ ...formData, selectFruitLarge: fruit })} 250 placeholder="Choose a fruit" 251 renderItem={(fruit) => `${fruit.emoji} ${fruit.name}`} 252 selectedItem={formData.selectFruitLarge || null} 253 size="large" 254 /> 255 </div> 256 </div> 257 </section> 258 259 <section> 260 <h3 className="mt-12 mb-8">Text Inputs</h3> 261 <div className={style.inputContainer}> 262 <TextInput placeholder="Basic text input" /> 263 <TextInput iconLeft={Icons.search} placeholder="With leftIcon" /> 264 <TextInput iconRight={Icons.delete} placeholder="With rightIcon" /> 265 <TextInput 266 iconLeft={Icons.search} 267 iconRight={Icons.delete} 268 placeholder="With leftIcon and rightIcon" 269 /> 270 <TextInput disabled placeholder="Disabled" /> 271 <TextInput 272 disabled 273 iconLeft={Icons.search} 274 iconRight={Icons.delete} 275 placeholder="Disabled with icons" 276 /> 277 <div className={style.inputRow}> 278 <TextInput 279 iconLeft={Icons.search} 280 iconRight={Icons.delete} 281 size="large" 282 placeholder="Button-adjacent" 283 /> 284 <button className="btn btn-lg" type="button"> 285 Primary 286 </button> 287 <button className="btn btn-lg btn-secondary" type="button"> 288 Secondary 289 </button> 290 </div> 291 <div className={style.inputRow}> 292 <TextInput 293 iconLeft={Icons.search} 294 iconRight={Icons.delete} 295 placeholder="Button-adjacent" 296 /> 297 <button className="btn" type="button"> 298 Primary 299 </button> 300 <button className="btn btn-secondary" type="button"> 301 Secondary 302 </button> 303 </div> 304 </div> 305 </section> 306 307 <section> 308 <h3 className="mt-12 mb-8">Buttons</h3> 309 310 {['btn-lg', '', 'btn-sm'].map((s, idx) => { 311 return ( 312 <div className="my-16"> 313 {['', 'btn-danger', 'btn-warning', 'btn-success'].map((v) => { 314 return ( 315 <div className="flex gap-4 my-6" key={`${idx}-${v}`}> 316 <button className={`btn ${s} ${v}`}>Button</button> 317 <a className={`btn ${s} ${v}`} href="#"> 318 Link 319 </a> 320 321 <button className={`btn ${s} ${v} btn-secondary`}>Button</button> 322 <a className={`btn ${s} ${v} btn-secondary`} href="#"> 323 Link 324 </a> 325 326 <button className={`btn ${s} ${v} btn-secondary`}> 327 <Icon icon={Icons.circleAdd} /> 328 Button 329 </button> 330 <a className={`btn ${s} ${v} btn-secondary`} href="#"> 331 <Icon icon={Icons.circleAdd} /> 332 Link 333 </a> 334 335 <button className={`btn ${s} ${v}`} disabled> 336 Button 337 </button> 338 <button className={`btn ${s} ${v} btn-secondary`} disabled> 339 <Icon icon={Icons.circleAdd} /> 340 Button 341 </button> 342 </div> 343 ); 344 })} 345 </div> 346 ); 347 })} 348 </section> 349 <section> 350 <Snackbars /> 351 </section> 352 </> 353 )} 354 </div> 355 </ContentContainer> 356 ); 357 }; 358 359 const Snackbars: React.FC = () => { 360 const intents = Object.keys(Intent); 361 return ( 362 <div> 363 <h3 className="mt-12 mb-8">Snackbars</h3> 364 365 {intents.map((i) => { 366 const onClick = () => { 367 switch (i) { 368 case 'danger': 369 danger('This is a danger snackbar.'); 370 break; 371 case 'success': 372 success('This is a success snackbar.'); 373 break; 374 case 'none': 375 info('This is an info snackbar.'); 376 break; 377 case 'warning': 378 warn('This is a warn snackbar with a very very very very very very very long message.'); 379 break; 380 } 381 }; 382 return ( 383 <button onClick={onClick} className={`btn btn-secondary mr-2`} key={i}> 384 {i} 385 </button> 386 ); 387 })} 388 </div> 389 ); 390 }; 391 392 const FRUITS: { emoji: string; name: string; id: string }[] = [ 393 { emoji: '🍎', name: 'apple (red)', id: 'red_apple' }, 394 { emoji: '🍏', name: 'apple (green)', id: 'green_apple' }, 395 { emoji: '🍌', name: 'banana', id: 'banana' }, 396 { emoji: '🍒', name: 'cherry', id: 'cherry' }, 397 { emoji: '🥥', name: 'coconut', id: 'coconut' }, 398 { emoji: '🍇', name: 'grape', id: 'grape' }, 399 { emoji: '🥝', name: 'kiwi', id: 'kiwi' }, 400 { emoji: '🍋', name: 'lemon', id: 'lemon' }, 401 { emoji: '🍈', name: 'melon', id: 'melon' }, 402 { emoji: '🍊', name: 'orange', id: 'orange' }, 403 { emoji: '🍑', name: 'peach', id: 'peach' }, 404 { emoji: '🍐', name: 'pear', id: 'pear' }, 405 { emoji: '🍍', name: 'pineapple', id: 'pineapple' }, 406 { emoji: '🍓', name: 'strawberry', id: 'strawberry' }, 407 { emoji: '🍉', name: 'watermelon', id: 'watermelon' }, 408 ]; 409 410 const FRUIT_NAMES = FRUITS.map((f) => f.name).slice(0, 5);