vitess.io/vitess@v0.16.2/web/vtadmin/src/components/ActionPanel.tsx (about) 1 /** 2 * Copyright 2022 The Vitess Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import React, { useState } from 'react'; 18 import { UseMutationResult } from 'react-query'; 19 20 import { Icon, Icons } from './Icon'; 21 import { TextInput } from './TextInput'; 22 23 type Mutation = UseMutationResult & { 24 mutate: () => void; 25 }; 26 27 export interface ActionPanelProps { 28 confirmationValue?: string; 29 danger?: boolean; 30 description: React.ReactNode; 31 disabled?: boolean; 32 documentationLink: string; 33 loadingText: string; 34 loadedText: string; 35 mutation: UseMutationResult; 36 title: string; 37 warnings?: React.ReactNodeArray; 38 body?: React.ReactNode; 39 } 40 /** 41 * ActionPanel is a panel used for initiating mutations on entity pages. 42 * When rendering multiple ActionPanel components, ensure they are in 43 * a surrounding <div> to ensure the first: and last: CSS selectors work. 44 */ 45 const ActionPanel: React.FC<ActionPanelProps> = ({ 46 confirmationValue, 47 danger, 48 disabled, 49 title, 50 description, 51 documentationLink, 52 mutation, 53 loadingText, 54 loadedText, 55 warnings = [], 56 body, 57 }) => { 58 const [typedConfirmation, setTypedConfirmation] = useState(''); 59 60 const requiresConfirmation = typeof confirmationValue === 'string' && !!confirmationValue; 61 62 const isDisabled = 63 !!disabled || mutation.isLoading || (requiresConfirmation && typedConfirmation !== confirmationValue); 64 65 return ( 66 <div 67 className={`p-9 pb-12 last:border-b border ${ 68 danger ? 'border-red-400' : 'border-gray-400' 69 } border-b-0 first:rounded-t-lg last:rounded-b-lg`} 70 title={title} 71 > 72 <div className="flex justify-between items-center"> 73 <p className="text-base font-bold m-0 text-gray-900">{title}</p> 74 <a 75 href={documentationLink} 76 target="_blank" 77 rel="noreferrer" 78 className="text-gray-900 ml-1 inline-block" 79 > 80 <span className="text-sm font-semibold text-gray-900">Documentation</span> 81 <Icon icon={Icons.open} className="ml-1 h-6 w-6 text-gray-900 fill-current inline" /> 82 </a> 83 </div> 84 <p className="text-base mt-0">{description}</p> 85 86 {warnings.map( 87 (warning, i) => 88 warning && ( 89 <div className="text-danger flex items-center" key={i}> 90 <Icon icon={Icons.alertFail} className="fill-current text-danger inline mr-2" /> 91 {warning} 92 </div> 93 ) 94 )} 95 96 {/* Don't render the confirmation input if "disabled" prop is set */} 97 {requiresConfirmation && !disabled && ( 98 <> 99 <p className="text-base"> 100 Please type <span className="font-bold">{confirmationValue}</span> confirm. 101 </p> 102 <div className="w-1/3"> 103 <TextInput value={typedConfirmation} onChange={(e) => setTypedConfirmation(e.target.value)} /> 104 </div> 105 </> 106 )} 107 {body} 108 <button 109 className={`btn btn-secondary ${danger && 'btn-danger'} mt-4`} 110 disabled={isDisabled} 111 onClick={() => { 112 (mutation as Mutation).mutate(); 113 setTypedConfirmation(''); 114 }} 115 > 116 {mutation.isLoading ? loadingText : loadedText} 117 </button> 118 </div> 119 ); 120 }; 121 122 export default ActionPanel;