@companix/uikit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.eslintrc +54 -0
  2. package/declaration.d.ts +4 -0
  3. package/index.html +12 -0
  4. package/package.json +66 -0
  5. package/playground/App.tsx +166 -0
  6. package/playground/Example.tsx +14 -0
  7. package/playground/Test.tsx +44 -0
  8. package/playground/animation-test-1/index.scss +20 -0
  9. package/playground/animation-test-1/index.tsx +17 -0
  10. package/playground/animation-test-2/index.scss +62 -0
  11. package/playground/animation-test-2/index.tsx +32 -0
  12. package/playground/bootstrap.tsx +19 -0
  13. package/playground/buttons/index.tsx +132 -0
  14. package/playground/checkbox/index.tsx +64 -0
  15. package/playground/date-input/index.tsx +45 -0
  16. package/playground/date-picker/index.tsx +41 -0
  17. package/playground/dialog/index.tsx +92 -0
  18. package/playground/dialog-alert/index.tsx +47 -0
  19. package/playground/drawer/index.tsx +55 -0
  20. package/playground/index.css +33 -0
  21. package/playground/index.scss +270 -0
  22. package/playground/input/index.tsx +112 -0
  23. package/playground/number-inputs/index.tsx +50 -0
  24. package/playground/popovers/index.tsx +70 -0
  25. package/playground/radio-group/index.tsx +69 -0
  26. package/playground/select/index.tsx +72 -0
  27. package/playground/select-tags/index.tsx +36 -0
  28. package/playground/styles.scss +2 -0
  29. package/playground/switch/index.tsx +44 -0
  30. package/playground/tabs/index.tsx +16 -0
  31. package/playground/test.scss +0 -0
  32. package/playground/text-area/index.tsx +17 -0
  33. package/playground/text-input/index.tsx +12 -0
  34. package/playground/toaster/index.tsx +156 -0
  35. package/playground/tooltip/index.tsx +26 -0
  36. package/src/Button/Button.scss +128 -0
  37. package/src/Button/index.tsx +72 -0
  38. package/src/ButtonGroup/ButtonGroup.scss +18 -0
  39. package/src/ButtonGroup/index.tsx +20 -0
  40. package/src/Checkbox/Checkbox.scss +115 -0
  41. package/src/Checkbox/index.tsx +46 -0
  42. package/src/Countdown/index.tsx +54 -0
  43. package/src/DateInput/DateInput.scss +11 -0
  44. package/src/DateInput/index.tsx +96 -0
  45. package/src/DatePicker/Calendar.scss +125 -0
  46. package/src/DatePicker/Calendar.tsx +157 -0
  47. package/src/DatePicker/CalendarHeader.tsx +139 -0
  48. package/src/DatePicker/DatePicker.scss +0 -0
  49. package/src/DatePicker/index.tsx +177 -0
  50. package/src/Dialog/Dialog.scss +25 -0
  51. package/src/Dialog/Popup.scss +55 -0
  52. package/src/Dialog/index.tsx +31 -0
  53. package/src/DialogAlert/Alert.scss +52 -0
  54. package/src/DialogAlert/Alert.tsx +78 -0
  55. package/src/DialogAlert/Viewport.tsx +52 -0
  56. package/src/DialogAlert/index.tsx +37 -0
  57. package/src/Drawer/Drawer.scss +112 -0
  58. package/src/Drawer/index.tsx +46 -0
  59. package/src/File/index.tsx +60 -0
  60. package/src/Form/Form.scss +70 -0
  61. package/src/Form/Input.scss +24 -0
  62. package/src/Form/index.tsx +131 -0
  63. package/src/Icon/icon.scss +18 -0
  64. package/src/Icon/index.tsx +43 -0
  65. package/src/LoadButton/index.tsx +17 -0
  66. package/src/NumberInput/index.tsx +74 -0
  67. package/src/OptionItem/Option.scss +89 -0
  68. package/src/OptionItem/OptionItem.tsx +49 -0
  69. package/src/OptionItem/OptionsList.tsx +26 -0
  70. package/src/Popover/Popover.scss +80 -0
  71. package/src/Popover/index.tsx +117 -0
  72. package/src/Radio/Radio.scss +148 -0
  73. package/src/Radio/index.tsx +68 -0
  74. package/src/Scrollable/ImitateScroll.tsx +141 -0
  75. package/src/Scrollable/Scrollable.scss +50 -0
  76. package/src/Scrollable/index.tsx +141 -0
  77. package/src/Select/Select.scss +80 -0
  78. package/src/Select/SelectInput.tsx +131 -0
  79. package/src/Select/index.tsx +134 -0
  80. package/src/SelectTags/SelectTags.scss +66 -0
  81. package/src/SelectTags/index.tsx +192 -0
  82. package/src/Spinner/Spinner.scss +14 -0
  83. package/src/Spinner/index.tsx +19 -0
  84. package/src/Stepper/StepperInput.scss +35 -0
  85. package/src/Stepper/index.tsx +76 -0
  86. package/src/Switch/Switch.scss +102 -0
  87. package/src/Switch/index.tsx +49 -0
  88. package/src/Tabs/Tabs.scss +58 -0
  89. package/src/Tabs/index.tsx +89 -0
  90. package/src/TextArea/TextArea.scss +34 -0
  91. package/src/TextArea/index.tsx +51 -0
  92. package/src/Toaster/RemoveListener.tsx +11 -0
  93. package/src/Toaster/Toast.tsx +69 -0
  94. package/src/Toaster/Toaster.scss +151 -0
  95. package/src/Toaster/Viewport.tsx +117 -0
  96. package/src/Toaster/index.tsx +52 -0
  97. package/src/Tooltip/Tooltip.scss +28 -0
  98. package/src/Tooltip/index.tsx +33 -0
  99. package/src/__hooks/use-frooze-closing.ts +51 -0
  100. package/src/__hooks/use-loading.ts +34 -0
  101. package/src/__hooks/use-local-storage.ts +19 -0
  102. package/src/__hooks/use-popover-position.ts +24 -0
  103. package/src/__hooks/use-previos.ts +25 -0
  104. package/src/__hooks/use-resize.ts +41 -0
  105. package/src/__hooks/use-scrollbox.ts +45 -0
  106. package/src/__hooks/use-stepper-input.ts +82 -0
  107. package/src/__hooks/use-update.ts +19 -0
  108. package/src/__hooks/useCalendar.ts +104 -0
  109. package/src/__hooks/useCalendarOptions-copy.ts +87 -0
  110. package/src/__hooks/useCalendarOptions.ts +68 -0
  111. package/src/__libs/calendar.ts +175 -0
  112. package/src/__utils/utils.ts +137 -0
  113. package/src/css.scss +120 -0
  114. package/src/index.scss +22 -0
  115. package/src/index.ts +36 -0
  116. package/src/mixins.scss +99 -0
  117. package/src/theme.scss +103 -0
  118. package/src/types.ts +14 -0
  119. package/tailwind.config.js +91 -0
  120. package/themes/classic/animations.scss +179 -0
  121. package/themes/classic/classic.scss +493 -0
  122. package/tsconfig.json +27 -0
  123. package/vite.build.ts +35 -0
  124. package/vite.config.ts +33 -0
@@ -0,0 +1,52 @@
1
+ @use '../mixins.scss';
2
+
3
+ .alert {
4
+ max-height: max-content;
5
+
6
+ @include mixins.use-styles(alert);
7
+
8
+ &-container {
9
+ @include mixins.use-styles(alert, container);
10
+ }
11
+
12
+ &-overlay {
13
+ @include mixins.use-styles(alert, overlay);
14
+ }
15
+
16
+ &-body {
17
+ display: flex;
18
+ flex-direction: row;
19
+
20
+ @include mixins.use-styles(alert, body);
21
+ }
22
+
23
+ &-title {
24
+ @include mixins.use-styles(alert, title);
25
+ }
26
+
27
+ &-description {
28
+ @include mixins.use-styles(alert, description);
29
+ }
30
+
31
+ &-icon {
32
+ @include mixins.use-styles(alert, icon);
33
+
34
+ svg {
35
+ @include mixins.use-size(alert, icon, size);
36
+ }
37
+ }
38
+
39
+ &-content {
40
+ display: flex;
41
+ flex-direction: column;
42
+
43
+ @include mixins.use-styles(alert, content);
44
+ }
45
+
46
+ &-footer {
47
+ display: flex;
48
+ justify-content: end;
49
+
50
+ @include mixins.use-styles(alert, footer);
51
+ }
52
+ }
@@ -0,0 +1,78 @@
1
+ import * as AlertPrimitive from '@radix-ui/react-alert-dialog'
2
+ import { Button } from '..'
3
+ import { InternButtonProps } from '../Button'
4
+ import { RemoveListener } from '../Toaster/RemoveListener'
5
+ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
6
+
7
+ interface ActionProps extends Omit<InternButtonProps, 'children'> {
8
+ onClick?: () => void
9
+ }
10
+
11
+ export interface AlertDialogProps {
12
+ open?: boolean
13
+ defaultOpen?: boolean
14
+ onOpenChange?: (value: boolean) => void
15
+ onUnMounted?: () => void
16
+ icon?: React.ReactNode
17
+ title?: string
18
+ description?: string
19
+ confirm?: ActionProps
20
+ cancel?: ActionProps
21
+ cancelDefaultText?: string
22
+ disableCancel?: boolean
23
+ }
24
+
25
+ export const AlertDialog = ({
26
+ open,
27
+ defaultOpen,
28
+ onOpenChange,
29
+ onUnMounted,
30
+ icon,
31
+ title,
32
+ description,
33
+ cancelDefaultText,
34
+ cancel,
35
+ disableCancel,
36
+ confirm
37
+ }: AlertDialogProps) => {
38
+ return (
39
+ <AlertPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
40
+ <RemoveListener callback={onUnMounted} />
41
+ <AlertPrimitive.Portal>
42
+ <AlertPrimitive.Overlay className="popup-overlay dialog-overlay" />
43
+ <AlertPrimitive.Content className="popup-container dialog-container">
44
+ <div className="popup alert">
45
+ <div className="alert-body">
46
+ {icon && <span className="alert-icon">{icon}</span>}
47
+ <div className="alert-content">
48
+ {title && <AlertPrimitive.Title className="alert-title">{title}</AlertPrimitive.Title>}
49
+ {!title && (
50
+ <VisuallyHidden>
51
+ <AlertPrimitive.Title />
52
+ </VisuallyHidden>
53
+ )}
54
+ {description && (
55
+ <AlertPrimitive.Description className="alert-description">
56
+ {description}
57
+ </AlertPrimitive.Description>
58
+ )}
59
+ </div>
60
+ </div>
61
+ <div className="alert-footer">
62
+ {!disableCancel && (cancel?.text || cancelDefaultText) && (
63
+ <AlertPrimitive.Cancel asChild>
64
+ <Button appearance="neutral" {...cancel} text={cancel?.text ?? cancelDefaultText} />
65
+ </AlertPrimitive.Cancel>
66
+ )}
67
+ {confirm?.text && (
68
+ <AlertPrimitive.Action asChild>
69
+ <Button appearance="negative" {...confirm} />
70
+ </AlertPrimitive.Action>
71
+ )}
72
+ </div>
73
+ </div>
74
+ </AlertPrimitive.Content>
75
+ </AlertPrimitive.Portal>
76
+ </AlertPrimitive.Root>
77
+ )
78
+ }
@@ -0,0 +1,52 @@
1
+ import { forwardRef, useImperativeHandle, useState } from 'react'
2
+ import { InnerAlert } from '.'
3
+ import { AlertDialog, AlertDialogProps } from './Alert'
4
+
5
+ export interface ViewportRef {
6
+ showAlert: (alert: InnerAlert) => void
7
+ }
8
+
9
+ export interface AlertBaseProps extends Pick<AlertDialogProps, 'cancelDefaultText'> {}
10
+
11
+ export const Viewport = forwardRef<ViewportRef, AlertBaseProps>((props, ref) => {
12
+ const [alerts, setAlerts] = useState<InnerAlert[]>([])
13
+
14
+ useImperativeHandle(
15
+ ref,
16
+ () => {
17
+ return {
18
+ showAlert: (alert) => {
19
+ setAlerts((state) => [...state, alert])
20
+ }
21
+ }
22
+ },
23
+ []
24
+ )
25
+
26
+ const handleClose = (id: string) => {
27
+ setAlerts((state) => {
28
+ const nextState = [...state]
29
+ const index = nextState.findIndex((item) => item.id === id)
30
+
31
+ if (index !== -1) {
32
+ nextState.splice(index, 1)
33
+ }
34
+
35
+ return nextState
36
+ })
37
+ }
38
+
39
+ return (
40
+ <>
41
+ {alerts.map(({ id, ...alert }) => (
42
+ <AlertDialog
43
+ defaultOpen
44
+ onUnMounted={() => handleClose(id)}
45
+ key={`alert-${id}`}
46
+ {...props}
47
+ {...alert}
48
+ />
49
+ ))}
50
+ </>
51
+ )
52
+ })
@@ -0,0 +1,37 @@
1
+ import { hash } from '@companix/utils-js'
2
+ import { useMemo, useRef } from 'react'
3
+ import { AlertDialogProps } from './Alert'
4
+ import { AlertBaseProps, Viewport, ViewportRef } from './Viewport'
5
+
6
+ export interface AlertOptions extends Omit<AlertDialogProps, 'open' | 'onOpenChange'> {}
7
+
8
+ export interface InnerAlert extends AlertOptions {
9
+ id: string
10
+ }
11
+
12
+ export const createAlertAgent = (options: AlertBaseProps = {}) => {
13
+ const store = {
14
+ emit: (alert: InnerAlert) => {
15
+ console.error('uninitialized', alert)
16
+ }
17
+ }
18
+
19
+ return {
20
+ show: (value: Omit<InnerAlert, 'id'>) => {
21
+ store.emit({ ...value, id: hash() })
22
+ },
23
+ Viewport: () => {
24
+ const ref = useRef<ViewportRef>(null)
25
+
26
+ useMemo(() => {
27
+ store.emit = (value) => {
28
+ if (ref.current) {
29
+ ref.current.showAlert(value)
30
+ }
31
+ }
32
+ }, [])
33
+
34
+ return <Viewport ref={ref} {...options} />
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,112 @@
1
+ @use '../mixins.scss';
2
+
3
+ .drawer {
4
+ display: flex;
5
+ flex-direction: column;
6
+ max-height: 100%;
7
+ z-index: 1000;
8
+ outline: none;
9
+ pointer-events: auto;
10
+ position: fixed;
11
+ z-index: 9999;
12
+
13
+ @include mixins.use-styles(drawer);
14
+
15
+ &-overlay {
16
+ inset: 0;
17
+ overflow: auto;
18
+ position: fixed;
19
+ -webkit-user-select: none;
20
+ -moz-user-select: none;
21
+ -ms-user-select: none;
22
+ user-select: none;
23
+ outline: none;
24
+ z-index: 9999;
25
+
26
+ @include mixins.use-styles(drawer, overlay);
27
+
28
+ &[data-state='open'] {
29
+ @include mixins.use-styles(drawer, overlay, in);
30
+ }
31
+
32
+ &[data-state='closed'] {
33
+ @include mixins.use-styles(drawer, overlay, out);
34
+ }
35
+ }
36
+
37
+ // animations
38
+
39
+ &[data-state='open'] {
40
+ &[data-direction='left'] {
41
+ @include mixins.use-styles(drawer, in, left);
42
+ }
43
+
44
+ &[data-direction='right'] {
45
+ @include mixins.use-styles(drawer, in, right);
46
+ }
47
+
48
+ &[data-direction='top'] {
49
+ @include mixins.use-styles(drawer, in, top);
50
+ }
51
+
52
+ &[data-direction='bottom'] {
53
+ @include mixins.use-styles(drawer, in, bottom);
54
+ }
55
+ }
56
+
57
+ &[data-state='closed'] {
58
+ &[data-direction='left'] {
59
+ @include mixins.use-styles(drawer, out, left);
60
+ }
61
+
62
+ &[data-direction='right'] {
63
+ @include mixins.use-styles(drawer, out, right);
64
+ }
65
+
66
+ &[data-direction='top'] {
67
+ @include mixins.use-styles(drawer, out, top);
68
+ }
69
+
70
+ &[data-direction='bottom'] {
71
+ @include mixins.use-styles(drawer, out, bottom);
72
+ }
73
+ }
74
+
75
+ // placement
76
+
77
+ &[data-direction='left'] {
78
+ top: 0;
79
+ left: 0;
80
+ bottom: 0;
81
+ width: var(--drawer-size, 50%);
82
+ border-top-right-radius: var(--drawer_radius, 0);
83
+ border-bottom-right-radius: var(--drawer_radius, 0);
84
+ }
85
+
86
+ &[data-direction='right'] {
87
+ top: 0;
88
+ right: 0;
89
+ bottom: 0;
90
+ width: var(--drawer-size, 50%);
91
+ border-top-left-radius: var(--drawer_radius, 0);
92
+ border-bottom-left-radius: var(--drawer_radius, 0);
93
+ }
94
+
95
+ &[data-direction='bottom'] {
96
+ left: 0;
97
+ right: 0;
98
+ bottom: 0;
99
+ height: var(--drawer-size, 50%);
100
+ border-top-right-radius: var(--drawer_radius, 0);
101
+ border-top-left-radius: var(--drawer_radius, 0);
102
+ }
103
+
104
+ &[data-direction='top'] {
105
+ left: 0;
106
+ right: 0;
107
+ top: 0;
108
+ height: var(--drawer-size, 50%);
109
+ border-bottom-right-radius: var(--drawer_radius, 0);
110
+ border-bottom-left-radius: var(--drawer_radius, 0);
111
+ }
112
+ }
@@ -0,0 +1,46 @@
1
+ import classNames from 'classnames'
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
3
+ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
4
+ import { varToStyle } from '@companix/utils-browser'
5
+
6
+ export interface DrawerProps {
7
+ open: boolean
8
+ onOpenChange: (value: boolean) => void
9
+ children: React.ReactNode
10
+ direction?: 'bottom' | 'top' | 'left' | 'right'
11
+ className?: string
12
+ /**
13
+ * CSS size of the drawer. This sets `width` if horizontal position (default)
14
+ * and `height` otherwise.
15
+ *
16
+ * Constants are available for common sizes:
17
+ * - `DrawerSize.SMALL = 360px`
18
+ * - `DrawerSize.STANDARD = 50%`
19
+ * - `DrawerSize.LARGE = 90%`
20
+ *
21
+ * @default DrawerSize.STANDARD = "50%"
22
+ */
23
+ size?: string
24
+ }
25
+
26
+ export const Drawer = ({ open, onOpenChange, children, size, direction, className }: DrawerProps) => {
27
+ return (
28
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
29
+ <DialogPrimitive.Portal>
30
+ <DialogPrimitive.Overlay className="drawer-overlay" />
31
+ <DialogPrimitive.Content
32
+ style={varToStyle({ '--drawer-size': size ?? '50%' })}
33
+ className={classNames('drawer', className)}
34
+ data-direction={direction}
35
+ >
36
+ <VisuallyHidden>
37
+ <DialogPrimitive.Title />
38
+ </VisuallyHidden>
39
+ {children}
40
+ </DialogPrimitive.Content>
41
+ </DialogPrimitive.Portal>
42
+ </DialogPrimitive.Root>
43
+ )
44
+ }
45
+
46
+ Drawer.Close = DialogPrimitive.Close
@@ -0,0 +1,60 @@
1
+ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
2
+ import { useRef } from 'react'
3
+
4
+ export interface FileOverlayProps {
5
+ multiple?: true
6
+ onChange?: (file: File[]) => void
7
+ children: React.ReactNode
8
+ mimes?: string[]
9
+ disabled?: boolean
10
+ className?: string
11
+ }
12
+
13
+ const FileOverlay = ({
14
+ onChange,
15
+ disabled,
16
+ mimes,
17
+ children,
18
+ multiple,
19
+ className
20
+ }: FileOverlayProps) => {
21
+ const ref = useRef<HTMLInputElement>(null)
22
+
23
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
24
+ if (disabled) {
25
+ return
26
+ }
27
+
28
+ if (e.target.files) {
29
+ onChange?.(Array.from(e.target.files))
30
+ }
31
+
32
+ if (ref.current) {
33
+ ref.current.value = ''
34
+ }
35
+ }
36
+
37
+ return (
38
+ <label className={className}>
39
+ {children}
40
+ <VisuallyHidden asChild>
41
+ <input
42
+ ref={ref}
43
+ hidden
44
+ type="file"
45
+ autoComplete="off"
46
+ autoCapitalize="none"
47
+ autoCorrect="off"
48
+ spellCheck="false"
49
+ aria-autocomplete="none"
50
+ multiple={multiple}
51
+ accept={(mimes ?? []).join(', ')}
52
+ onChange={handleChange}
53
+ disabled={disabled}
54
+ />
55
+ </VisuallyHidden>
56
+ </label>
57
+ )
58
+ }
59
+
60
+ export { FileOverlay }
@@ -0,0 +1,70 @@
1
+ @use '../mixins.scss';
2
+
3
+ @mixin use-size($size) {
4
+ &[data-size='#{$size}'] {
5
+ .form-input {
6
+ @include mixins.use-styles(form, size, $size);
7
+ }
8
+ }
9
+ }
10
+
11
+ .form {
12
+ position: relative;
13
+
14
+ @include mixins.use-styles(form);
15
+
16
+ @include use-size(sm);
17
+ @include use-size(md);
18
+ @include use-size(lg);
19
+
20
+ &-space-margin {
21
+ margin: 0px var(--form_space, 0);
22
+ }
23
+
24
+ &:hover {
25
+ @include mixins.use-styles(form, hover);
26
+ }
27
+
28
+ &:active {
29
+ @include mixins.use-styles(form, active);
30
+ }
31
+
32
+ &[data-fill] {
33
+ width: 100%;
34
+
35
+ .form-input {
36
+ width: 100%;
37
+ }
38
+ }
39
+
40
+ &[data-required] {
41
+ @include mixins.use-styles(form, required);
42
+ }
43
+
44
+ &[data-disabled] {
45
+ @include mixins.use-styles(form, disabled);
46
+
47
+ .form-input {
48
+ cursor: inherit;
49
+ }
50
+ }
51
+
52
+ &:focus-within {
53
+ @include mixins.use-styles(form, focus);
54
+ }
55
+ }
56
+
57
+ .form-input {
58
+ outline: none;
59
+
60
+ &::-webkit-outer-spin-button,
61
+ &::-webkit-inner-spin-button {
62
+ -webkit-appearance: none;
63
+ }
64
+
65
+ &::placeholder {
66
+ user-select: none;
67
+
68
+ @include mixins.use-styles(form, placeholder);
69
+ }
70
+ }
@@ -0,0 +1,24 @@
1
+ .form-input-base {
2
+ padding: 0px var(--form_space, 0);
3
+ border-radius: var(--form_border-radius);
4
+
5
+ &-left-element {
6
+ position: absolute;
7
+ left: 0;
8
+ top: 0;
9
+ bottom: 0;
10
+ pointer-events: none;
11
+ display: flex;
12
+ align-items: center;
13
+ }
14
+
15
+ &-right-element {
16
+ position: absolute;
17
+ right: 0;
18
+ top: 0;
19
+ bottom: 0;
20
+ pointer-events: none;
21
+ display: flex;
22
+ align-items: center;
23
+ }
24
+ }
@@ -0,0 +1,131 @@
1
+ import cn from 'classnames'
2
+ import InputMask from 'react-input-mask'
3
+
4
+ import { useLayoutAndUpdate } from '../__hooks/use-update'
5
+ import { attr } from '@companix/utils-browser'
6
+ import { forwardRef, useCallback, useRef } from 'react'
7
+ import { mergeRefs } from 'react-merge-refs'
8
+
9
+ export interface FormProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ required?: boolean
11
+ disabled?: boolean
12
+ leftElement?: React.ReactNode
13
+ rightElement?: React.ReactNode
14
+ placeholder?: string
15
+ value?: string | number
16
+ readOnly?: boolean
17
+ onValueChange?: (value: string, targetElement: HTMLInputElement) => void
18
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
19
+ inputRef?: React.RefObject<HTMLInputElement>
20
+ size?: 'sm' | 'md' | 'lg'
21
+ fill?: boolean
22
+ mask?: string
23
+ maskChar?: string
24
+ }
25
+
26
+ export const Form = forwardRef<HTMLDivElement, FormProps>(
27
+ (
28
+ {
29
+ required,
30
+ size,
31
+ fill,
32
+ leftElement,
33
+ rightElement,
34
+ onChange,
35
+ onValueChange,
36
+ readOnly,
37
+ className,
38
+ value,
39
+ placeholder,
40
+ disabled,
41
+ mask,
42
+ maskChar,
43
+ inputRef: clientInputRef,
44
+ ...containerProps
45
+ },
46
+ ref
47
+ ) => {
48
+ const inputRef = useRef<HTMLInputElement>(null)
49
+
50
+ const rightRef = useRef<HTMLSpanElement>(null)
51
+ const leftRef = useRef<HTMLSpanElement>(null)
52
+
53
+ const elements = { Right: rightRef, Left: leftRef }
54
+
55
+ const updateInputWidth = useCallback((side: 'Left' | 'Right') => {
56
+ if (inputRef.current) {
57
+ const input = inputRef.current.style
58
+ const element = elements[side]
59
+
60
+ if (element.current && element.current.clientWidth) {
61
+ if (input[`padding${side}`] !== `${element.current.clientWidth}px`) {
62
+ input[`padding${side}`] = `${element.current.clientWidth}px`
63
+ }
64
+ } else {
65
+ if (input[`padding${side}`]) {
66
+ input[`padding${side}`] = ''
67
+ }
68
+ }
69
+ }
70
+ }, [])
71
+
72
+ useLayoutAndUpdate(() => {
73
+ updateInputWidth('Left')
74
+ updateInputWidth('Right')
75
+ }, [rightElement, leftElement])
76
+
77
+ const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
78
+ onChange?.(event)
79
+ onValueChange?.(event.target.value, event.target)
80
+ }, [])
81
+
82
+ return (
83
+ <div
84
+ ref={ref}
85
+ className={cn('form', className)}
86
+ data-size={size ?? 'md'}
87
+ data-fill={attr(fill)}
88
+ data-required={attr(required)}
89
+ data-disabled={attr(disabled)}
90
+ {...containerProps}
91
+ >
92
+ {leftElement && (
93
+ <span ref={leftRef} className="form-input-base-left-element">
94
+ {leftElement}
95
+ </span>
96
+ )}
97
+ <Input
98
+ type="text"
99
+ ref={mergeRefs([inputRef, clientInputRef])}
100
+ className="form-input form-input-base"
101
+ aria-disabled={disabled}
102
+ onChange={handleInputChange}
103
+ value={value}
104
+ placeholder={placeholder}
105
+ disabled={disabled}
106
+ readOnly={readOnly}
107
+ maskChar={maskChar}
108
+ mask={mask}
109
+ />
110
+ {rightElement && (
111
+ <span ref={rightRef} className="form-input-base-right-element">
112
+ {rightElement}
113
+ </span>
114
+ )}
115
+ </div>
116
+ )
117
+ }
118
+ )
119
+
120
+ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'children'> {
121
+ mask?: string
122
+ maskChar?: string
123
+ }
124
+
125
+ const Input = forwardRef<HTMLInputElement, InputProps>(({ mask, maskChar, ...inputProps }, ref) => {
126
+ if (mask) {
127
+ return <InputMask inputRef={ref} mask={mask} maskChar={maskChar} {...inputProps} />
128
+ }
129
+
130
+ return <input ref={ref} {...inputProps} />
131
+ })
@@ -0,0 +1,18 @@
1
+ @use '../mixins.scss';
2
+
3
+ $icon-sizes: xxxs, xxs, xs, s, m, l, xl, xxl, xxxl;
4
+
5
+ @each $size in $icon-sizes {
6
+ .icon-size-#{$size} {
7
+ @include mixins.use-size(icon, size, $size);
8
+ }
9
+ }
10
+
11
+ :where(svg:not([fill])) {
12
+ fill: currentcolor;
13
+ }
14
+
15
+ .icon {
16
+ // display: inline-block;
17
+ flex-shrink: 0;
18
+ }