Website for showcasing all features of Tesseract Web.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

379 rivejä
8.6 KiB

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