|
| 1 | +import { utilClasses } from './utility.js'; |
| 2 | +import { useState } from 'react'; |
| 3 | +import { Toggle } from '../components/Toggle/index.js'; |
| 4 | +import { RadioButtonGroup } from '../components/RadioButtonGroup/index.js'; |
| 5 | +import { clsx } from './clsx.js'; |
| 6 | +import styles from './utilityStories.module.css'; |
| 7 | +import { Body } from '../components/Body/index.js'; |
| 8 | +import { userEvent } from 'storybook/test'; |
| 9 | + |
| 10 | +export default { |
| 11 | + title: 'Features/Utility Classes', |
| 12 | + tags: ['status:stable'], |
| 13 | + parameters: { |
| 14 | + chromatic: { |
| 15 | + disableSnapshot: true, |
| 16 | + }, |
| 17 | + }, |
| 18 | +}; |
| 19 | + |
| 20 | +async function pressTab() { |
| 21 | + await userEvent.tab(); |
| 22 | +} |
| 23 | + |
| 24 | +export const Center = () => { |
| 25 | + const [className, setClassName] = useState<string | undefined>( |
| 26 | + utilClasses.center, |
| 27 | + ); |
| 28 | + return ( |
| 29 | + <> |
| 30 | + <div className={clsx(styles.wrapper, className)}> |
| 31 | + <Body>{className ? 'Centered' : 'Not centered'}</Body> |
| 32 | + </div> |
| 33 | + <Toggle |
| 34 | + label={'Apply center class name'} |
| 35 | + checked={className !== undefined} |
| 36 | + className={utilClasses.marginTopGiga} |
| 37 | + onChange={() => |
| 38 | + setClassName((current) => (current ? undefined : utilClasses.center)) |
| 39 | + } |
| 40 | + /> |
| 41 | + </> |
| 42 | + ); |
| 43 | +}; |
| 44 | + |
| 45 | +export const HideVisually = () => { |
| 46 | + const [className, setClassName] = useState<string | undefined>( |
| 47 | + utilClasses.hideVisually, |
| 48 | + ); |
| 49 | + |
| 50 | + return ( |
| 51 | + <> |
| 52 | + <Body className={className}>Hidden</Body> |
| 53 | + <Toggle |
| 54 | + label={'Apply hideVisually class name'} |
| 55 | + checked={className === utilClasses.hideVisually} |
| 56 | + className={utilClasses.marginTopGiga} |
| 57 | + onChange={() => |
| 58 | + setClassName((current) => |
| 59 | + current ? undefined : utilClasses.hideVisually, |
| 60 | + ) |
| 61 | + } |
| 62 | + /> |
| 63 | + </> |
| 64 | + ); |
| 65 | +}; |
| 66 | + |
| 67 | +export const HideScrollbar = () => { |
| 68 | + const [className, setClassName] = useState<string | undefined>( |
| 69 | + utilClasses.hideScrollbar, |
| 70 | + ); |
| 71 | + |
| 72 | + return ( |
| 73 | + <> |
| 74 | + <div className={clsx(styles.wrapper, className)}> |
| 75 | + <div className={styles.scrollable}>Scroll me</div> |
| 76 | + </div> |
| 77 | + <Toggle |
| 78 | + label={'Apply hideScrollbar class name'} |
| 79 | + checked={className === utilClasses.hideScrollbar} |
| 80 | + className={utilClasses.marginTopGiga} |
| 81 | + onChange={() => |
| 82 | + setClassName((current) => |
| 83 | + current ? undefined : utilClasses.hideScrollbar, |
| 84 | + ) |
| 85 | + } |
| 86 | + /> |
| 87 | + </> |
| 88 | + ); |
| 89 | +}; |
| 90 | + |
| 91 | +export const FocusVisible = () => { |
| 92 | + const [className, setClassName] = useState<string | undefined>( |
| 93 | + utilClasses.focusVisible, |
| 94 | + ); |
| 95 | + |
| 96 | + return ( |
| 97 | + <> |
| 98 | + <div |
| 99 | + // biome-ignore lint/a11y/noNoninteractiveTabindex: for demo purposes only |
| 100 | + tabIndex={0} |
| 101 | + className={className} |
| 102 | + > |
| 103 | + I'm an interactive div |
| 104 | + </div> |
| 105 | + <Toggle |
| 106 | + label={'Apply focusVisible class name'} |
| 107 | + checked={className === utilClasses.focusVisible} |
| 108 | + className={utilClasses.marginTopGiga} |
| 109 | + onChange={() => |
| 110 | + setClassName((current) => |
| 111 | + current ? undefined : utilClasses.focusVisible, |
| 112 | + ) |
| 113 | + } |
| 114 | + /> |
| 115 | + </> |
| 116 | + ); |
| 117 | +}; |
| 118 | +FocusVisible.play = pressTab; |
| 119 | + |
| 120 | +export const FocusVisibleInset = () => { |
| 121 | + const [className, setClassName] = useState<string | undefined>( |
| 122 | + utilClasses.focusVisibleInset, |
| 123 | + ); |
| 124 | + |
| 125 | + return ( |
| 126 | + <> |
| 127 | + <div |
| 128 | + // biome-ignore lint/a11y/noNoninteractiveTabindex: for demo purposes only |
| 129 | + tabIndex={0} |
| 130 | + className={className} |
| 131 | + > |
| 132 | + I'm an interactive div |
| 133 | + </div> |
| 134 | + <Toggle |
| 135 | + label={'Apply focusVisibleInset class name'} |
| 136 | + checked={className === utilClasses.focusVisibleInset} |
| 137 | + className={utilClasses.marginTopGiga} |
| 138 | + onChange={() => |
| 139 | + setClassName((current) => |
| 140 | + current ? undefined : utilClasses.focusVisibleInset, |
| 141 | + ) |
| 142 | + } |
| 143 | + /> |
| 144 | + </> |
| 145 | + ); |
| 146 | +}; |
| 147 | +FocusVisibleInset.play = pressTab; |
| 148 | + |
| 149 | +const sizes = [ |
| 150 | + 'Bit', |
| 151 | + 'Byte', |
| 152 | + 'Kilo', |
| 153 | + 'Mega', |
| 154 | + 'Giga', |
| 155 | + 'Tera', |
| 156 | + 'Peta', |
| 157 | + 'Exa', |
| 158 | + 'Zetta', |
| 159 | + 'Yotta', |
| 160 | + 'Ronna', |
| 161 | + 'Quetta', |
| 162 | +] as const; |
| 163 | +type Size = (typeof sizes)[number]; |
| 164 | + |
| 165 | +export const Spacing = () => { |
| 166 | + const sizeOptions = sizes.map((size) => ({ label: size, value: size })); |
| 167 | + const [allDirections, setAllDirections] = useState(true); |
| 168 | + const [allDirectionsSpacingValue, setAllDirectionsSpacingValue] = |
| 169 | + useState<Size>('Bit'); |
| 170 | + const [spacingTopValue, setSpacingTopValue] = useState<Size>('Bit'); |
| 171 | + const [spacingBottomValue, setSpacingBottomValue] = useState<Size>('Bit'); |
| 172 | + const [spacingLeftValue, setSpacingLeftValue] = useState<Size>('Bit'); |
| 173 | + const [spacingRightValue, setSpacingRightValue] = useState<Size>('Bit'); |
| 174 | + const className = allDirections |
| 175 | + ? utilClasses[`margin${allDirectionsSpacingValue}`] |
| 176 | + : clsx( |
| 177 | + utilClasses[`marginTop${spacingTopValue}`], |
| 178 | + utilClasses[`marginBottom${spacingBottomValue}`], |
| 179 | + utilClasses[`marginLeft${spacingLeftValue}`], |
| 180 | + utilClasses[`marginRight${spacingRightValue}`], |
| 181 | + ); |
| 182 | + |
| 183 | + return ( |
| 184 | + <> |
| 185 | + <div className={clsx(styles.wrapper, styles.flex)}> |
| 186 | + <div |
| 187 | + className={clsx( |
| 188 | + className, |
| 189 | + styles.content, |
| 190 | + styles.flex, |
| 191 | + utilClasses.center, |
| 192 | + )} |
| 193 | + > |
| 194 | + {allDirections && ( |
| 195 | + <Body color="on-strong"> |
| 196 | + className applied: {`margin${allDirectionsSpacingValue}`} |
| 197 | + </Body> |
| 198 | + )} |
| 199 | + {!allDirections && ( |
| 200 | + <> |
| 201 | + <Body |
| 202 | + color="on-strong" |
| 203 | + className={styles.left} |
| 204 | + >{`marginLeft${spacingLeftValue}`}</Body> |
| 205 | + |
| 206 | + <Body |
| 207 | + color="on-strong" |
| 208 | + className={styles.top} |
| 209 | + >{`marginTop${spacingTopValue}`}</Body> |
| 210 | + <Body |
| 211 | + color="on-strong" |
| 212 | + className={styles.bottom} |
| 213 | + >{`marginBottom${spacingBottomValue}`}</Body> |
| 214 | + |
| 215 | + <Body color="on-strong" className={styles.right}> |
| 216 | + {' '} |
| 217 | + {`marginRight${spacingRightValue}`} |
| 218 | + </Body> |
| 219 | + </> |
| 220 | + )} |
| 221 | + </div> |
| 222 | + </div> |
| 223 | + <Toggle |
| 224 | + label={'Apply spacing to all directions'} |
| 225 | + checked={allDirections} |
| 226 | + className={clsx( |
| 227 | + utilClasses.marginTopGiga, |
| 228 | + utilClasses.marginBottomGiga, |
| 229 | + )} |
| 230 | + onChange={() => setAllDirections((current) => !current)} |
| 231 | + /> |
| 232 | + {allDirections ? ( |
| 233 | + <RadioButtonGroup |
| 234 | + value={allDirectionsSpacingValue} |
| 235 | + onChange={(event) => |
| 236 | + setAllDirectionsSpacingValue(event.target.value as Size) |
| 237 | + } |
| 238 | + options={sizeOptions} |
| 239 | + label={'Spacing value'} |
| 240 | + /> |
| 241 | + ) : ( |
| 242 | + <div className={styles.directions}> |
| 243 | + <RadioButtonGroup |
| 244 | + value={spacingTopValue} |
| 245 | + onChange={(event) => setSpacingTopValue(event.target.value as Size)} |
| 246 | + options={sizeOptions} |
| 247 | + label={'Spacing top value'} |
| 248 | + /> |
| 249 | + <RadioButtonGroup |
| 250 | + value={spacingBottomValue} |
| 251 | + onChange={(event) => |
| 252 | + setSpacingBottomValue(event.target.value as Size) |
| 253 | + } |
| 254 | + options={sizeOptions} |
| 255 | + label={'Spacing bottom value'} |
| 256 | + /> |
| 257 | + <RadioButtonGroup |
| 258 | + value={spacingLeftValue} |
| 259 | + onChange={(event) => |
| 260 | + setSpacingLeftValue(event.target.value as Size) |
| 261 | + } |
| 262 | + options={sizeOptions} |
| 263 | + label={'Spacing left value'} |
| 264 | + /> |
| 265 | + <RadioButtonGroup |
| 266 | + value={spacingRightValue} |
| 267 | + onChange={(event) => |
| 268 | + setSpacingRightValue(event.target.value as Size) |
| 269 | + } |
| 270 | + options={sizeOptions} |
| 271 | + label={'Spacing right value'} |
| 272 | + /> |
| 273 | + </div> |
| 274 | + )} |
| 275 | + </> |
| 276 | + ); |
| 277 | +}; |
0 commit comments