@sikka/hawa 0.0.229 → 0.0.231

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.
@@ -0,0 +1,369 @@
1
+ import React, { useRef, useState, useEffect } from "react"
2
+ import { RichTextarea } from "rich-textarea"
3
+ import clsx from "clsx"
4
+
5
+ const Property = (props) => {
6
+ return (
7
+ <div
8
+ className="border-box mr-[5px] flex h-[32px] w-[32px] items-center justify-center rounded bg-gray-400 p-2"
9
+ onClick={props.onClick}
10
+ >
11
+ {props.name}
12
+ </div>
13
+ )
14
+ }
15
+
16
+ type ComponentTypes = {
17
+ foo?: string
18
+ }
19
+
20
+ // TODO: Handle copy pasting by assigning to local storage ?
21
+
22
+ const styleClasses = {
23
+ bold: "font-bold",
24
+ italic: "italic",
25
+ under: "underline",
26
+ strike: "line-through",
27
+ }
28
+
29
+ export const FloatingComment: React.FunctionComponent<ComponentTypes> = (
30
+ props
31
+ ) => {
32
+ const [text, setText] = useState({
33
+ content: "",
34
+ stylings: [], // A styling is an object with 2 indices specifying a substring of text, and the applied effect
35
+ })
36
+
37
+ const field = useRef(null)
38
+
39
+ // utility functions
40
+ const getRange = (start, end) => {
41
+ let result = []
42
+ for (let i = start; i <= end; i++) {
43
+ result.push(i)
44
+ }
45
+ return result
46
+ }
47
+
48
+ const intersection = (setA, setB) => {
49
+ const _intersection = new Set()
50
+ for (const elem of setB) {
51
+ if (setA.has(elem)) {
52
+ _intersection.add(elem)
53
+ }
54
+ }
55
+ return _intersection
56
+ }
57
+
58
+ const getMinimum = (array) => {
59
+ return array.sort((a, b) => a - b)[0]
60
+ }
61
+
62
+ const getMaximum = (array) => {
63
+ return array.sort((a, b) => b - a)[0]
64
+ }
65
+
66
+ // -1 - types dont match
67
+ // 0 - s1 is surrounded or on the edge of the s2
68
+ // 1 - s1 intersects with s2
69
+ // 2 - s1 does not intersect with s2
70
+ const getCorrelation = (styling1, styling2) => {
71
+ if (styling1.type != styling2.type) return -1
72
+
73
+ if (
74
+ styling2.start <= styling1.start &&
75
+ styling2.finish >= styling1.finish
76
+ ) {
77
+ return 0
78
+ }
79
+
80
+ let indices1 = new Set(getRange(styling1.start - 1, styling1.finish - 1))
81
+ let indices2 = new Set(getRange(styling2.start - 1, styling2.finish - 1))
82
+
83
+ let result = intersection(indices1, indices2)
84
+
85
+ return result.size == 0 ? 2 : 1
86
+ }
87
+
88
+ // Correlation handler
89
+ const stylingSplice = (correlations, stylings, current, type) => {
90
+ // Only one surround correlation is possible at one time, so use .find to fetch it
91
+
92
+ let [_, index, styling] = correlations.find(([c, _, __]) => c == 0)
93
+
94
+ // Remove correlated styling
95
+ stylings = stylings.filter((_, _index) => _index != index)
96
+
97
+ // Get splices
98
+ let added = [
99
+ {
100
+ type: type,
101
+ start: getMinimum([styling.start, current.start]),
102
+ finish: getMaximum([styling.start, current.start]),
103
+ },
104
+ {
105
+ type: type,
106
+ start: getMinimum([styling.finish, current.finish]),
107
+ finish: getMaximum([styling.finish, current.finish]),
108
+ },
109
+ ]
110
+
111
+ // Remove empty splices (edge cases)
112
+ added = added.filter((item) => item.start != item.finish)
113
+
114
+ // Add to current stylings
115
+ stylings = [...stylings, ...added]
116
+
117
+ return stylings
118
+ }
119
+
120
+ // Correlation handler
121
+ const stylingIntersect = (correlations, stylings, current, type) => {
122
+ // Filter out all intersected stylings
123
+ let intersections = correlations
124
+ .filter(([c, _, __]) => c == 1)
125
+ .map(([_, index, styling]) => {
126
+ return [index, styling]
127
+ })
128
+
129
+ // Add current styling with no index for the sake for endpoint indices
130
+ intersections.push([-1, current])
131
+
132
+ // Get minimum intersection start index
133
+ let start = intersections
134
+ .map(([_, styling]) => styling.start)
135
+ .sort((a, b) => a - b)[0]
136
+
137
+ // Get maximum intersection start index
138
+ let finish = intersections
139
+ .map(([_, styling]) => styling.finish)
140
+ .sort((a, b) => b - a)[0]
141
+
142
+ // Get indices of all intersection
143
+ let indices = intersections.map((e) => e[0])
144
+
145
+ // Remove all from resulting styling array
146
+ stylings = stylings.filter((_, index) => !indices.includes(index))
147
+
148
+ // Add widest styling which encompasses all intersections
149
+ stylings.push({
150
+ type: type,
151
+ start: start,
152
+ finish: finish,
153
+ })
154
+
155
+ return stylings
156
+ }
157
+
158
+ const perform = (id) => {
159
+ let stylings = text.stylings.slice()
160
+ var selectionStart = field.current.selectionStart
161
+ var selectionEnd = field.current.selectionEnd
162
+
163
+ if (selectionStart == selectionEnd) return
164
+
165
+ let current = {
166
+ type: id,
167
+ start: selectionStart,
168
+ finish: selectionEnd,
169
+ }
170
+
171
+ let correlations = []
172
+
173
+ // Check the correlation between this requested styling and all other stylings
174
+ for (let i = 0; i < stylings.length; i++) {
175
+ let styling = stylings[i]
176
+ let correlation = getCorrelation(current, styling)
177
+
178
+ if (correlation != -1) correlations.push([correlation, i, styling])
179
+ }
180
+
181
+ let result
182
+
183
+ if (correlations.find(([c, _, __]) => c == 1)) {
184
+ result = stylingIntersect(correlations, stylings, current, id)
185
+ } else if (correlations.find(([c, _, __]) => c == 0)) {
186
+ result = stylingSplice(correlations, stylings, current, id)
187
+ } else if (
188
+ correlations.find(([c, _, __]) => c == 2) ||
189
+ correlations.length == 0
190
+ ) {
191
+ result = [...stylings, current]
192
+ }
193
+
194
+ setText({
195
+ ...text,
196
+ stylings: result,
197
+ })
198
+ }
199
+
200
+ // Get stylings encompassing an index within it's range
201
+ const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
202
+ // Find all stylings with encompassing range
203
+ let matches = text.stylings.filter(
204
+ ({ start, finish }) =>
205
+ index >= start + startOffset && index < finish + finishOffset
206
+ )
207
+
208
+ return matches
209
+ }
210
+
211
+ // Get stylings after an index
212
+ const getSucceedStylings = (index) => {
213
+ // Find all stylings after the index
214
+ let matches = text.stylings.filter(({ start, finish }) => start >= index)
215
+
216
+ return matches
217
+ }
218
+
219
+ const getStylingIndex = (styling) => {
220
+ return text.stylings.findIndex(
221
+ (item) =>
222
+ item.start == styling.start &&
223
+ item.finish == styling.finish &&
224
+ item.type == styling.type
225
+ )
226
+ }
227
+
228
+ const onChange = (value) => {
229
+ let difference = value.length - text.content.length
230
+
231
+ let selection = field.current.selectionStart - difference
232
+
233
+ let stylings = text.stylings.slice()
234
+ let succeeding = getSucceedStylings(selection)
235
+ let changes = []
236
+
237
+ for (let succeed of succeeding) {
238
+ let index = getStylingIndex(succeed)
239
+ let styling = stylings[index]
240
+
241
+ changes.push([
242
+ index,
243
+ styling.start + difference,
244
+ styling.finish + difference,
245
+ ])
246
+ }
247
+
248
+ let intersecting = getIntersectStylings(selection, 1, 1)
249
+
250
+ for (let intersect of intersecting) {
251
+ let index = getStylingIndex(intersect)
252
+ let styling = stylings[index]
253
+
254
+ changes.push([index, styling.start, styling.finish + difference])
255
+ }
256
+
257
+ let spliced = []
258
+ for (let [index, start, finish] of changes) {
259
+ stylings[index] = {
260
+ ...stylings[index],
261
+ start: start,
262
+ finish: finish,
263
+ }
264
+
265
+ if (start >= finish) spliced.push(index)
266
+ }
267
+
268
+ stylings = stylings.filter((_, index) => !spliced.includes(index))
269
+
270
+ setText({ ...text, content: value, stylings: stylings })
271
+ }
272
+
273
+ return (
274
+ <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-gray-300 shadow-md">
275
+ <div className={clsx("flex w-full flex-row justify-start p-2")}>
276
+ <Property
277
+ name="B"
278
+ onClick={() => {
279
+ perform("bold")
280
+ }}
281
+ />
282
+ <Property
283
+ name="I"
284
+ onClick={() => {
285
+ perform("italic")
286
+ }}
287
+ />
288
+ <Property
289
+ name="U"
290
+ onClick={() => {
291
+ perform("under")
292
+ }}
293
+ />
294
+ <Property
295
+ name="S"
296
+ onClick={() => {
297
+ perform("strike")
298
+ }}
299
+ />
300
+ </div>
301
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
302
+ <div className="w-full">
303
+ <RichTextarea
304
+ ref={field}
305
+ value={text.content}
306
+ style={{ width: "100%" }} // tailwind w-full does not work
307
+ className="order-none h-[150px] resize-none p-2 outline-none"
308
+ onChange={(e) => {
309
+ onChange(e.target.value)
310
+ }}
311
+ >
312
+ {(value) => {
313
+ // Get all styling indices
314
+ let indices = text.stylings
315
+ .map(({ start, finish }) => [start, finish])
316
+ .flat()
317
+
318
+ // Sort ascendingly
319
+ indices = indices.sort((a, b) => a - b)
320
+
321
+ // Remove duplicates
322
+ indices = indices.filter(
323
+ (element, index) => indices.indexOf(element) == index
324
+ )
325
+
326
+ // Add first index if not present
327
+ if (indices[0] != 0) indices.unshift(0)
328
+
329
+ // Add last index if not present
330
+ let last = text.content.length
331
+ if (indices[indices.length - 1] != last) indices.push(last)
332
+
333
+ let result = []
334
+
335
+ for (let i = 0; i < indices.length - 1; i++) {
336
+ result.push([
337
+ indices[i],
338
+ value.substring(indices[i], indices[i + 1]),
339
+ ])
340
+ }
341
+
342
+ return result.map((_data, index) => {
343
+ let [start, data] = _data
344
+
345
+ // Get stylings encompassing an index within it's range
346
+ let stylings = getIntersectStylings(start)
347
+
348
+ return (
349
+ <span
350
+ key={index}
351
+ className={`${stylings
352
+ .map((styling) => styleClasses[styling.type])
353
+ .join(" ")}
354
+ `}
355
+ >
356
+ {data}
357
+ </span>
358
+ )
359
+ })
360
+ }}
361
+ </RichTextarea>
362
+ </div>
363
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
364
+ <button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
365
+ Submit
366
+ </button>
367
+ </div>
368
+ )
369
+ }
@@ -31,6 +31,8 @@ export * from "./UsageCard"
31
31
  export * from "./InvoiceAccordion"
32
32
  export * from "./HawaDatepicker"
33
33
  export * from "./UserFeedback"
34
+ export * from "./ArrowCarousel"
35
+ export * from "./FloatingComment"
34
36
  // Inputs
35
37
  export * from "./HawaTextField"
36
38
  export * from "./HawaCardInput"
package/src/styles.css CHANGED
@@ -660,6 +660,9 @@ video {
660
660
  .z-50 {
661
661
  z-index: 50;
662
662
  }
663
+ .order-none {
664
+ order: 0;
665
+ }
663
666
  .m-0 {
664
667
  margin: 0px;
665
668
  }
@@ -771,6 +774,9 @@ video {
771
774
  .mr-40 {
772
775
  margin-right: 10rem;
773
776
  }
777
+ .mr-\[5px\] {
778
+ margin-right: 5px;
779
+ }
774
780
  .mt-0 {
775
781
  margin-top: 0px;
776
782
  }
@@ -798,6 +804,9 @@ video {
798
804
  .mt-8 {
799
805
  margin-top: 2rem;
800
806
  }
807
+ .box-border {
808
+ box-sizing: border-box;
809
+ }
801
810
  .block {
802
811
  display: block;
803
812
  }
@@ -882,12 +891,18 @@ video {
882
891
  .h-96 {
883
892
  height: 24rem;
884
893
  }
894
+ .h-\[150px\] {
895
+ height: 150px;
896
+ }
885
897
  .h-\[1px\] {
886
898
  height: 1px;
887
899
  }
888
900
  .h-\[2\.36rem\] {
889
901
  height: 2.36rem;
890
902
  }
903
+ .h-\[32px\] {
904
+ height: 32px;
905
+ }
891
906
  .h-\[calc\(100\%-3\.5rem\)\] {
892
907
  height: calc(100% - 3.5rem);
893
908
  }
@@ -901,6 +916,10 @@ video {
901
916
  .h-full {
902
917
  height: 100%;
903
918
  }
919
+ .h-min {
920
+ height: -moz-min-content;
921
+ height: min-content;
922
+ }
904
923
  .h-screen {
905
924
  height: 100vh;
906
925
  }
@@ -983,6 +1002,12 @@ video {
983
1002
  .w-8 {
984
1003
  width: 2rem;
985
1004
  }
1005
+ .w-\[32px\] {
1006
+ width: 32px;
1007
+ }
1008
+ .w-\[400px\] {
1009
+ width: 400px;
1010
+ }
986
1011
  .w-\[calc\(1\%\)\] {
987
1012
  width: calc(1%);
988
1013
  }
@@ -996,6 +1021,10 @@ video {
996
1021
  .w-full {
997
1022
  width: 100%;
998
1023
  }
1024
+ .w-min {
1025
+ width: -moz-min-content;
1026
+ width: min-content;
1027
+ }
999
1028
  .min-w-\[24px\] {
1000
1029
  min-width: 24px;
1001
1030
  }
@@ -1093,6 +1122,9 @@ video {
1093
1122
  .cursor-pointer {
1094
1123
  cursor: pointer;
1095
1124
  }
1125
+ .resize-none {
1126
+ resize: none;
1127
+ }
1096
1128
  .resize {
1097
1129
  resize: both;
1098
1130
  }
@@ -1481,6 +1513,10 @@ video {
1481
1513
  .bg-buttonPrimary-500 {
1482
1514
  background-color: var(--button-primary-500);
1483
1515
  }
1516
+ .bg-cyan-800 {
1517
+ --tw-bg-opacity: 1;
1518
+ background-color: rgb(21 94 117 / var(--tw-bg-opacity));
1519
+ }
1484
1520
  .bg-gray-100 {
1485
1521
  --tw-bg-opacity: 1;
1486
1522
  background-color: rgb(243 244 246 / var(--tw-bg-opacity));
@@ -1493,6 +1529,10 @@ video {
1493
1529
  --tw-bg-opacity: 1;
1494
1530
  background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1495
1531
  }
1532
+ .bg-gray-400 {
1533
+ --tw-bg-opacity: 1;
1534
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1535
+ }
1496
1536
  .bg-gray-50 {
1497
1537
  --tw-bg-opacity: 1;
1498
1538
  background-color: rgb(249 250 251 / var(--tw-bg-opacity));
@@ -1555,6 +1595,10 @@ video {
1555
1595
  --tw-bg-opacity: 1;
1556
1596
  background-color: rgb(185 28 28 / var(--tw-bg-opacity));
1557
1597
  }
1598
+ .bg-slate-600 {
1599
+ --tw-bg-opacity: 1;
1600
+ background-color: rgb(71 85 105 / var(--tw-bg-opacity));
1601
+ }
1558
1602
  .bg-transparent {
1559
1603
  background-color: transparent;
1560
1604
  }
@@ -1687,6 +1731,10 @@ video {
1687
1731
  padding-top: 1.25rem;
1688
1732
  padding-bottom: 1.25rem;
1689
1733
  }
1734
+ .py-6 {
1735
+ padding-top: 1.5rem;
1736
+ padding-bottom: 1.5rem;
1737
+ }
1690
1738
  .pb-2 {
1691
1739
  padding-bottom: 0.5rem;
1692
1740
  }
@@ -1797,6 +1845,9 @@ video {
1797
1845
  .capitalize {
1798
1846
  text-transform: capitalize;
1799
1847
  }
1848
+ .italic {
1849
+ font-style: italic;
1850
+ }
1800
1851
  .leading-4 {
1801
1852
  line-height: 1rem;
1802
1853
  }
@@ -1885,6 +1936,10 @@ video {
1885
1936
  -webkit-text-decoration-line: underline;
1886
1937
  text-decoration-line: underline;
1887
1938
  }
1939
+ .line-through {
1940
+ -webkit-text-decoration-line: line-through;
1941
+ text-decoration-line: line-through;
1942
+ }
1888
1943
  .underline-offset-4 {
1889
1944
  text-underline-offset: 4px;
1890
1945
  }
@@ -1930,6 +1985,10 @@ video {
1930
1985
  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
1931
1986
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1932
1987
  }
1988
+ .outline-none {
1989
+ outline: 2px solid transparent;
1990
+ outline-offset: 2px;
1991
+ }
1933
1992
  .outline {
1934
1993
  outline-style: solid;
1935
1994
  }
@@ -2178,6 +2237,11 @@ body {
2178
2237
  color: rgb(156 163 175 / var(--tw-text-opacity));
2179
2238
  }
2180
2239
 
2240
+ .hover\:text-gray-500:hover {
2241
+ --tw-text-opacity: 1;
2242
+ color: rgb(107 114 128 / var(--tw-text-opacity));
2243
+ }
2244
+
2181
2245
  .hover\:text-gray-900:hover {
2182
2246
  --tw-text-opacity: 1;
2183
2247
  color: rgb(17 24 39 / var(--tw-text-opacity));
@@ -2592,4 +2656,4 @@ body {
2592
2656
  margin-left: 2.5rem;
2593
2657
  margin-right: 2.5rem;
2594
2658
  }
2595
- }
2659
+ }
package/src/tailwind.css CHANGED
@@ -28,4 +28,4 @@
28
28
 
29
29
  body {
30
30
  font-family: "IBM Plex Sans Arabic", sans-serif;
31
- }
31
+ }