Design system.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

401 lignes
9.0 KiB

  1. import { css } from '@tesseract-design/goofy-goober';
  2. export enum TextControlSize {
  3. SMALL = 'small',
  4. MEDIUM = 'medium',
  5. LARGE = 'large',
  6. }
  7. export enum TextControlStyle {
  8. DEFAULT = 'default',
  9. ALTERNATE = 'alternate',
  10. }
  11. export const MIN_HEIGHTS: Record<TextControlSize, string> = {
  12. [TextControlSize.SMALL]: '2.5rem',
  13. [TextControlSize.MEDIUM]: '3rem',
  14. [TextControlSize.LARGE]: '4rem',
  15. };
  16. const LABEL_VERTICAL_PADDING_SIZES: Record<TextControlSize, string> = {
  17. [TextControlSize.SMALL]: '0.125rem',
  18. [TextControlSize.MEDIUM]: '0.25rem',
  19. [TextControlSize.LARGE]: '0.375rem',
  20. };
  21. const INPUT_FONT_SIZES: Record<TextControlSize, string> = {
  22. [TextControlSize.SMALL]: '0.75em',
  23. [TextControlSize.MEDIUM]: '0.85em',
  24. [TextControlSize.LARGE]: '1em',
  25. };
  26. const SECONDARY_TEXT_SIZES: Record<TextControlSize, string> = {
  27. [TextControlSize.SMALL]: '0.6em',
  28. [TextControlSize.MEDIUM]: '0.725em',
  29. [TextControlSize.LARGE]: '0.85em',
  30. };
  31. const MULTILINE_VERTICAL_PADDING_FACTORS: Record<TextControlSize, string> = {
  32. [TextControlSize.SMALL]: '1.25',
  33. [TextControlSize.MEDIUM]: '1.2',
  34. [TextControlSize.LARGE]: '1.45',
  35. };
  36. const ALTERNATE_VERTICAL_PADDING_FACTORS: Record<TextControlSize, string> = {
  37. [TextControlSize.SMALL]: '1.75',
  38. [TextControlSize.MEDIUM]: '1.35',
  39. [TextControlSize.LARGE]: '1.25',
  40. };
  41. export type TextControlBaseArgs = {
  42. /**
  43. * Will the component occupy the whole width of its container?
  44. */
  45. block: boolean,
  46. /**
  47. * Stylistic variant of the component.
  48. */
  49. style: TextControlStyle,
  50. /**
  51. * Will the component display a surrounding border?
  52. */
  53. border: boolean,
  54. /**
  55. * Does the component include an additional indicator for labels?
  56. */
  57. indicator: boolean,
  58. /**
  59. * Size of the component.
  60. */
  61. size: TextControlSize,
  62. /**
  63. * Can the size of the component be changed?
  64. */
  65. resizable: boolean,
  66. /**
  67. * Does this component have predefined values?
  68. */
  69. predefinedValues: boolean,
  70. }
  71. export const Root = ({
  72. block,
  73. }: TextControlBaseArgs): string => css.cx(
  74. css`
  75. vertical-align: middle;
  76. position: relative;
  77. border-radius: 0.25rem;
  78. font-family: var(--font-family-base, sans-serif);
  79. max-width: 100%;
  80. &:focus-within {
  81. --color-accent: var(--color-hover, red);
  82. }
  83. & > span {
  84. border-color: var(--color-accent, blue);
  85. box-sizing: border-box;
  86. display: inline-block;
  87. border-width: 0.125rem;
  88. border-style: solid;
  89. position: absolute;
  90. top: 0;
  91. left: 0;
  92. width: 100%;
  93. height: 100%;
  94. border-radius: inherit;
  95. z-index: 2;
  96. pointer-events: none;
  97. transition-property: border-color;
  98. }
  99. & > span::before {
  100. position: absolute;
  101. top: 0;
  102. left: 0;
  103. width: 100%;
  104. height: 100%;
  105. content: '';
  106. border-radius: 0.125rem;
  107. opacity: 0.5;
  108. pointer-events: none;
  109. box-shadow: 0 0 0 0 var(--color-accent, blue);
  110. transition-property: box-shadow;
  111. transition-duration: 150ms;
  112. transition-timing-function: linear;
  113. }
  114. &:focus-within > span::before {
  115. box-shadow: 0 0 0 0.375rem var(--color-accent, blue);
  116. }
  117. `,
  118. css.dynamic({
  119. display: block ? 'block' : 'inline-block',
  120. }),
  121. );
  122. export const LabelWrapper = ({
  123. style,
  124. border,
  125. indicator,
  126. size,
  127. }: TextControlBaseArgs): string => css.cx(
  128. css`
  129. color: var(--color-accent, blue);
  130. box-sizing: border-box;
  131. position: absolute;
  132. top: 0;
  133. left: 0;
  134. width: 100%;
  135. overflow: hidden;
  136. text-overflow: ellipsis;
  137. white-space: nowrap;
  138. font-weight: bolder;
  139. z-index: 1;
  140. pointer-events: none;
  141. transition-property: color;
  142. line-height: 0.65;
  143. user-select: none;
  144. `,
  145. css.dynamic({
  146. 'padding-bottom': LABEL_VERTICAL_PADDING_SIZES[size],
  147. 'font-size': SECONDARY_TEXT_SIZES[size],
  148. }),
  149. css.if (border) (
  150. css`
  151. background-color: var(--color-bg, white);
  152. `
  153. ),
  154. css.if (style === TextControlStyle.ALTERNATE) (
  155. css.dynamic({
  156. 'padding-top': `calc(${LABEL_VERTICAL_PADDING_SIZES[size]} * 0.5)`,
  157. }),
  158. css.if (border) (
  159. css`
  160. padding-left: 0.5rem;
  161. `,
  162. css.dynamic({
  163. 'padding-right': indicator ? MIN_HEIGHTS[size] : '0.5rem',
  164. }),
  165. ),
  166. css.if (!border && indicator) (
  167. css.dynamic({
  168. 'padding-right': MIN_HEIGHTS[size],
  169. }),
  170. ),
  171. ),
  172. css.if (style === TextControlStyle.DEFAULT) (
  173. css`
  174. padding-left: 0.5rem;
  175. `,
  176. css.dynamic({
  177. 'padding-top': LABEL_VERTICAL_PADDING_SIZES[size],
  178. 'padding-right': !indicator ? '0.5rem' : MIN_HEIGHTS[size],
  179. }),
  180. ),
  181. )
  182. export const Input = ({
  183. style,
  184. size,
  185. indicator,
  186. border,
  187. resizable,
  188. predefinedValues,
  189. }: TextControlBaseArgs): string => css.cx(
  190. css`
  191. appearance: none;
  192. display: block;
  193. box-sizing: border-box;
  194. position: relative;
  195. border: 0;
  196. border-radius: inherit;
  197. margin: 0;
  198. font-family: inherit;
  199. min-width: 8rem;
  200. max-width: 100%;
  201. width: 100%;
  202. z-index: 1;
  203. transition-property: background-color, color;
  204. &:focus {
  205. outline: 0;
  206. color: var(--color-fg, black);
  207. }
  208. &:disabled {
  209. cursor: not-allowed;
  210. opacity: 0.5;
  211. }
  212. &:disabled ~ * {
  213. opacity: 0.5;
  214. }
  215. `,
  216. css.media('only screen') (
  217. css`
  218. background-color: var(--color-bg, white);
  219. color: var(--color-fg, black);
  220. `
  221. ),
  222. css.dynamic({
  223. 'min-height': MIN_HEIGHTS[size],
  224. 'font-size': INPUT_FONT_SIZES[size],
  225. }),
  226. css.if (resizable) (
  227. css`
  228. resize: vertical;
  229. `
  230. ),
  231. css.if (predefinedValues) (
  232. css`
  233. cursor: pointer;
  234. `
  235. ),
  236. css.if (border) (
  237. css`
  238. background-color: var(--color-bg, white);
  239. `
  240. ),
  241. css.if (style === TextControlStyle.ALTERNATE) (
  242. css`
  243. padding-bottom: 0;
  244. `,
  245. css.dynamic({
  246. 'padding-top': resizable
  247. ? `calc(${SECONDARY_TEXT_SIZES[size]} * 2.5)`
  248. : `calc(${SECONDARY_TEXT_SIZES[size]} * 2)`,
  249. 'line-height': `calc(${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 1.1)`,
  250. }),
  251. css.if (border) (
  252. css`
  253. padding-left: 0.5rem;
  254. `,
  255. css.dynamic({
  256. 'padding-right': indicator ? MIN_HEIGHTS[size] : '0.5rem',
  257. }),
  258. ),
  259. css.if (!border && indicator) (
  260. css.dynamic({
  261. 'padding-right': MIN_HEIGHTS[size],
  262. }),
  263. )
  264. ),
  265. css.if (style === TextControlStyle.DEFAULT) (
  266. css`
  267. padding-left: 1rem;
  268. `,
  269. css.dynamic({
  270. 'padding-right': !indicator ? '1rem' : MIN_HEIGHTS[size],
  271. 'line-height': `calc(${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 1.1)`,
  272. }),
  273. css.if (resizable) (
  274. css.dynamic({
  275. 'padding-top': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]})`,
  276. 'padding-bottom': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]})`,
  277. })
  278. ),
  279. css.if (!resizable) (
  280. css.dynamic({
  281. 'padding-bottom': `calc(${SECONDARY_TEXT_SIZES[size]} * ${MULTILINE_VERTICAL_PADDING_FACTORS[size]} * 0.5)`,
  282. })
  283. )
  284. ),
  285. )
  286. export const HintWrapper = ({
  287. style,
  288. size,
  289. border,
  290. }: TextControlBaseArgs): string => css.cx(
  291. css`
  292. box-sizing: border-box;
  293. position: absolute;
  294. left: 0;
  295. font-size: 0.85em;
  296. max-width: 100%;
  297. overflow: hidden;
  298. text-overflow: ellipsis;
  299. white-space: nowrap;
  300. z-index: 1;
  301. pointer-events: none;
  302. user-select: none;
  303. line-height: 0;
  304. `,
  305. css.if (border) (
  306. css`
  307. background-color: var(--color-bg, white);
  308. `
  309. ),
  310. css.if (style === TextControlStyle.ALTERNATE) (
  311. css`
  312. line-height: 1.25;
  313. `,
  314. css.dynamic({
  315. top: `calc(${SECONDARY_TEXT_SIZES[size]} * ${ALTERNATE_VERTICAL_PADDING_FACTORS[size]})`,
  316. 'font-size': SECONDARY_TEXT_SIZES[size],
  317. }),
  318. css.if (border) (
  319. css`
  320. padding-left: 0.5rem;
  321. &:last-child {
  322. padding-right: 0.5rem;
  323. }
  324. `,
  325. css.dynamic({
  326. 'padding-right': MIN_HEIGHTS[size],
  327. })
  328. )
  329. ),
  330. css.if (style === TextControlStyle.DEFAULT) (
  331. css`
  332. bottom: 0;
  333. padding-left: 1rem;
  334. line-height: 1.25;
  335. &:last-child {
  336. padding-right: 1rem;
  337. }
  338. `,
  339. css.dynamic({
  340. 'padding-bottom': `calc(${LABEL_VERTICAL_PADDING_SIZES[size]} * 0.9)`,
  341. 'padding-right': MIN_HEIGHTS[size],
  342. 'font-size': SECONDARY_TEXT_SIZES[size],
  343. })
  344. )
  345. )
  346. export const Hint = (): string => css.cx(
  347. css`
  348. opacity: 0.5;
  349. `
  350. );
  351. export const IndicatorWrapper = ({
  352. size
  353. }: TextControlBaseArgs): string => css.cx(
  354. css`
  355. color: var(--color-accent, blue);
  356. box-sizing: border-box;
  357. position: absolute;
  358. bottom: 0;
  359. right: 0;
  360. display: grid;
  361. place-content: center;
  362. padding: 0 1rem;
  363. z-index: 2;
  364. pointer-events: none;
  365. transition-property: color;
  366. line-height: 1;
  367. user-select: none;
  368. `,
  369. css.dynamic({
  370. width: MIN_HEIGHTS[size],
  371. height: MIN_HEIGHTS[size],
  372. }),
  373. );
  374. export const Indicator = (): string => css.cx(
  375. css`
  376. width: 1.5em;
  377. height: 1.5em;
  378. fill: none;
  379. stroke: currentColor;
  380. stroke-width: 2;
  381. stroke-linecap: round;
  382. stroke-linejoin: round;
  383. `,
  384. );