@sikka/hawa 0.0.230 → 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
+ }
@@ -32,6 +32,7 @@ export * from "./InvoiceAccordion"
32
32
  export * from "./HawaDatepicker"
33
33
  export * from "./UserFeedback"
34
34
  export * from "./ArrowCarousel"
35
+ export * from "./FloatingComment"
35
36
  // Inputs
36
37
  export * from "./HawaTextField"
37
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
  }
@@ -885,12 +891,18 @@ video {
885
891
  .h-96 {
886
892
  height: 24rem;
887
893
  }
894
+ .h-\[150px\] {
895
+ height: 150px;
896
+ }
888
897
  .h-\[1px\] {
889
898
  height: 1px;
890
899
  }
891
900
  .h-\[2\.36rem\] {
892
901
  height: 2.36rem;
893
902
  }
903
+ .h-\[32px\] {
904
+ height: 32px;
905
+ }
894
906
  .h-\[calc\(100\%-3\.5rem\)\] {
895
907
  height: calc(100% - 3.5rem);
896
908
  }
@@ -990,6 +1002,12 @@ video {
990
1002
  .w-8 {
991
1003
  width: 2rem;
992
1004
  }
1005
+ .w-\[32px\] {
1006
+ width: 32px;
1007
+ }
1008
+ .w-\[400px\] {
1009
+ width: 400px;
1010
+ }
993
1011
  .w-\[calc\(1\%\)\] {
994
1012
  width: calc(1%);
995
1013
  }
@@ -1104,6 +1122,9 @@ video {
1104
1122
  .cursor-pointer {
1105
1123
  cursor: pointer;
1106
1124
  }
1125
+ .resize-none {
1126
+ resize: none;
1127
+ }
1107
1128
  .resize {
1108
1129
  resize: both;
1109
1130
  }
@@ -1492,6 +1513,10 @@ video {
1492
1513
  .bg-buttonPrimary-500 {
1493
1514
  background-color: var(--button-primary-500);
1494
1515
  }
1516
+ .bg-cyan-800 {
1517
+ --tw-bg-opacity: 1;
1518
+ background-color: rgb(21 94 117 / var(--tw-bg-opacity));
1519
+ }
1495
1520
  .bg-gray-100 {
1496
1521
  --tw-bg-opacity: 1;
1497
1522
  background-color: rgb(243 244 246 / var(--tw-bg-opacity));
@@ -1504,6 +1529,10 @@ video {
1504
1529
  --tw-bg-opacity: 1;
1505
1530
  background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1506
1531
  }
1532
+ .bg-gray-400 {
1533
+ --tw-bg-opacity: 1;
1534
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1535
+ }
1507
1536
  .bg-gray-50 {
1508
1537
  --tw-bg-opacity: 1;
1509
1538
  background-color: rgb(249 250 251 / var(--tw-bg-opacity));
@@ -1566,6 +1595,10 @@ video {
1566
1595
  --tw-bg-opacity: 1;
1567
1596
  background-color: rgb(185 28 28 / var(--tw-bg-opacity));
1568
1597
  }
1598
+ .bg-slate-600 {
1599
+ --tw-bg-opacity: 1;
1600
+ background-color: rgb(71 85 105 / var(--tw-bg-opacity));
1601
+ }
1569
1602
  .bg-transparent {
1570
1603
  background-color: transparent;
1571
1604
  }
@@ -1812,6 +1845,9 @@ video {
1812
1845
  .capitalize {
1813
1846
  text-transform: capitalize;
1814
1847
  }
1848
+ .italic {
1849
+ font-style: italic;
1850
+ }
1815
1851
  .leading-4 {
1816
1852
  line-height: 1rem;
1817
1853
  }
@@ -1900,6 +1936,10 @@ video {
1900
1936
  -webkit-text-decoration-line: underline;
1901
1937
  text-decoration-line: underline;
1902
1938
  }
1939
+ .line-through {
1940
+ -webkit-text-decoration-line: line-through;
1941
+ text-decoration-line: line-through;
1942
+ }
1903
1943
  .underline-offset-4 {
1904
1944
  text-underline-offset: 4px;
1905
1945
  }
@@ -1945,6 +1985,10 @@ video {
1945
1985
  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
1946
1986
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1947
1987
  }
1988
+ .outline-none {
1989
+ outline: 2px solid transparent;
1990
+ outline-offset: 2px;
1991
+ }
1948
1992
  .outline {
1949
1993
  outline-style: solid;
1950
1994
  }
@@ -2612,4 +2656,4 @@ body {
2612
2656
  margin-left: 2.5rem;
2613
2657
  margin-right: 2.5rem;
2614
2658
  }
2615
- }
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
+ }