@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sikka/hawa",
3
- "version": "0.0.229",
3
+ "version": "0.0.231",
4
4
  "description": "SaaS Oriented UI Kit",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.es.js",
@@ -48,10 +48,10 @@
48
48
  "@storybook/react": "^6.5.8",
49
49
  "@storybook/storybook-deployer": "^2.8.11",
50
50
  "@storybook/theming": "^6.5.8",
51
+ "@swc/core": "^1.3.53",
51
52
  "@types/react": "^18.0.25",
52
53
  "@types/react-dom": "^18.0.9",
53
54
  "babel-loader": "^8.2.2",
54
- "@swc/core": "^1.3.53",
55
55
  "clsx": "^1.2.1",
56
56
  "color": "^4.2.3",
57
57
  "crypto-js": "^4.1.1",
@@ -111,6 +111,7 @@
111
111
  "react-hook-form": "^7.28.0",
112
112
  "react-icons": "^4.6.0",
113
113
  "react-select": "^5.3.2",
114
+ "rich-textarea": "^0.21.1",
114
115
  "rollup-plugin-typescript2": "^0.34.1",
115
116
  "start": "^5.1.0"
116
117
  }
@@ -0,0 +1,86 @@
1
+ import React from "react"
2
+
3
+ import { FaArrowLeft, FaArrowRight } from "react-icons/fa"
4
+
5
+ type Item = {
6
+ label?: string
7
+ icon?: JSX.Element
8
+ }
9
+
10
+ type ComponentTypes = {
11
+ items: Item[]
12
+ index?: number
13
+ arrowSize?: number
14
+ labelSize?: "small" | "medium" | "big"
15
+ }
16
+
17
+ const Arrow = (props: {
18
+ icon: any
19
+ size: number
20
+ onClick?: () => void
21
+ disabled?: boolean
22
+ }) => {
23
+ return (
24
+ <props.icon
25
+ className={
26
+ props.disabled || false ? "text-gray-300" : "hover:text-gray-500"
27
+ }
28
+ size={props.size}
29
+ onClick={props.onClick || (() => {})}
30
+ />
31
+ )
32
+ }
33
+
34
+ export const ArrowCarousel: React.FunctionComponent<ComponentTypes> = (
35
+ props
36
+ ) => {
37
+ const [index, setIndex] = React.useState(props.index || 0)
38
+
39
+ React.useEffect(() => {
40
+ console.log(`INDEX CHANGED TO: ${index}`)
41
+ }, [index])
42
+
43
+ const sizes = {
44
+ small: ["", -8],
45
+ medium: ["2", -11],
46
+ big: ["3", -16],
47
+ }
48
+
49
+ return (
50
+ <div className="align-center box-boorder relative flex h-min w-min flex-row items-center justify-center rounded bg-white p-4 py-6 shadow-md">
51
+ <Arrow
52
+ icon={FaArrowLeft}
53
+ size={props.arrowSize || 48}
54
+ disabled={index == 0}
55
+ onClick={() => {
56
+ if (index != 0) setIndex(index - 1)
57
+ }}
58
+ />
59
+
60
+ <div
61
+ className={`relative box-border flex h-min flex-col items-center justify-center p-5`}
62
+ >
63
+ <div>{props.items[index].icon}</div>
64
+ <div
65
+ className={`absolute bottom-0 text-${
66
+ sizes[props.labelSize || "small"][0]
67
+ }xl `}
68
+ style={{
69
+ marginBottom: sizes[props.labelSize || "small"][1],
70
+ }}
71
+ >
72
+ {props.items[index].label}
73
+ </div>
74
+ </div>
75
+
76
+ <Arrow
77
+ icon={FaArrowRight}
78
+ size={props.arrowSize || 48}
79
+ disabled={index == props.items.length - 1}
80
+ onClick={() => {
81
+ if (index != props.items.length - 1) setIndex(index + 1)
82
+ }}
83
+ />
84
+ </div>
85
+ )
86
+ }
@@ -0,0 +1,514 @@
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
+ onMouseDown={props.onMouseDown}
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
+ revert: [0, 0],
36
+ })
37
+
38
+ const field = useRef(null)
39
+
40
+ useEffect(() => {
41
+ let [start, end] = text.revert
42
+
43
+ if (start == 0 && end == 0) return
44
+
45
+ console.log(start)
46
+ console.log(end)
47
+
48
+ // TODO: To get the child node based on the index, get lengths of each child node
49
+ // add to each child node the sum of all previous child nodes
50
+ // Create ranges for 0 - n, n - n1, n - n2, ... where nx is the length of a child node
51
+ // Add 1 to the index
52
+ // Iterate through each range, and check if the index is greater than the start of the range, and less than or equal the end of the range
53
+ let startNode = null
54
+ let endNode = null
55
+
56
+ let total = 0
57
+ let nodes = Array.from(field.current.childNodes)
58
+ for (let i = 0; i < nodes.length; i++) {
59
+ let node: any = nodes[i]
60
+ let sum = node.textContent.length + total
61
+
62
+ if (start > total && start <= sum) {
63
+ startNode = nodes[i]
64
+ }
65
+
66
+ if (end > total && end <= sum) {
67
+ endNode = nodes[i]
68
+ }
69
+
70
+ total += node.textContent.length
71
+ }
72
+
73
+ // console.log(
74
+ // Array.from(field.current.childNodes).map(
75
+ // (item) => item.textContent.length
76
+ // )
77
+ // )
78
+
79
+ console.log(startNode)
80
+ console.log(endNode)
81
+
82
+ var range = document.createRange()
83
+ var sel = window.getSelection()
84
+
85
+ range.setStart(startNode.firstChild, start)
86
+ range.setEnd(endNode.firstChild, end)
87
+ range.collapse(true)
88
+
89
+ sel.removeAllRanges()
90
+ sel.addRange(range)
91
+ }, [text.revert])
92
+
93
+ // utility functions
94
+ const getRange = (start, end) => {
95
+ let result = []
96
+ for (let i = start; i <= end; i++) {
97
+ result.push(i)
98
+ }
99
+ return result
100
+ }
101
+
102
+ const intersection = (setA, setB) => {
103
+ const _intersection = new Set()
104
+ for (const elem of setB) {
105
+ if (setA.has(elem)) {
106
+ _intersection.add(elem)
107
+ }
108
+ }
109
+ return _intersection
110
+ }
111
+
112
+ const getMinimum = (array) => {
113
+ return array.sort((a, b) => a - b)[0]
114
+ }
115
+
116
+ const getMaximum = (array) => {
117
+ return array.sort((a, b) => b - a)[0]
118
+ }
119
+
120
+ // -1 - types dont match
121
+ // 0 - s1 is surrounded or on the edge of the s2
122
+ // 1 - s1 intersects with s2
123
+ // 2 - s1 does not intersect with s2
124
+ const getCorrelation = (styling1, styling2) => {
125
+ if (styling1.type != styling2.type) return -1
126
+
127
+ if (
128
+ styling2.start <= styling1.start &&
129
+ styling2.finish >= styling1.finish
130
+ ) {
131
+ return 0
132
+ }
133
+
134
+ let indices1 = new Set(getRange(styling1.start - 1, styling1.finish - 1))
135
+ let indices2 = new Set(getRange(styling2.start - 1, styling2.finish - 1))
136
+
137
+ let result = intersection(indices1, indices2)
138
+
139
+ return result.size == 0 ? 2 : 1
140
+ }
141
+
142
+ // Correlation handler
143
+ const stylingSplice = (correlations, stylings, current, type) => {
144
+ // Only one surround correlation is possible at one time, so use .find to fetch it
145
+
146
+ let [_, index, styling] = correlations.find(([c, _, __]) => c == 0)
147
+
148
+ // Remove correlated styling
149
+ stylings = stylings.filter((_, _index) => _index != index)
150
+
151
+ // Get splices
152
+ let added = [
153
+ {
154
+ type: type,
155
+ start: getMinimum([styling.start, current.start]),
156
+ finish: getMaximum([styling.start, current.start]),
157
+ },
158
+ {
159
+ type: type,
160
+ start: getMinimum([styling.finish, current.finish]),
161
+ finish: getMaximum([styling.finish, current.finish]),
162
+ },
163
+ ]
164
+
165
+ // Remove empty splices (edge cases)
166
+ added = added.filter((item) => item.start != item.finish)
167
+
168
+ // Add to current stylings
169
+ stylings = [...stylings, ...added]
170
+
171
+ return stylings
172
+ }
173
+
174
+ // Correlation handler
175
+ const stylingIntersect = (correlations, stylings, current, type) => {
176
+ // Filter out all intersected stylings
177
+ let intersections = correlations
178
+ .filter(([c, _, __]) => c == 1)
179
+ .map(([_, index, styling]) => {
180
+ return [index, styling]
181
+ })
182
+
183
+ // Add current styling with no index for the sake for endpoint indices
184
+ intersections.push([-1, current])
185
+
186
+ // Get minimum intersection start index
187
+ let start = intersections
188
+ .map(([_, styling]) => styling.start)
189
+ .sort((a, b) => a - b)[0]
190
+
191
+ // Get maximum intersection start index
192
+ let finish = intersections
193
+ .map(([_, styling]) => styling.finish)
194
+ .sort((a, b) => b - a)[0]
195
+
196
+ // Get indices of all intersection
197
+ let indices = intersections.map((e) => e[0])
198
+
199
+ // Remove all from resulting styling array
200
+ stylings = stylings.filter((_, index) => !indices.includes(index))
201
+
202
+ // Add widest styling which encompasses all intersections
203
+ stylings.push({
204
+ type: type,
205
+ start: start,
206
+ finish: finish,
207
+ })
208
+
209
+ return stylings
210
+ }
211
+
212
+ const perform = (id) => {
213
+ let stylings = text.stylings.slice()
214
+ let [selectionStart, selectionEnd] = getFieldSelection()
215
+
216
+ if (selectionStart == selectionEnd) return
217
+
218
+ let current = {
219
+ type: id,
220
+ start: selectionStart,
221
+ finish: selectionEnd,
222
+ }
223
+
224
+ let correlations = []
225
+
226
+ // Check the correlation between this requested styling and all other stylings
227
+ for (let i = 0; i < stylings.length; i++) {
228
+ let styling = stylings[i]
229
+ let correlation = getCorrelation(current, styling)
230
+
231
+ if (correlation != -1) correlations.push([correlation, i, styling])
232
+ }
233
+
234
+ let result
235
+
236
+ if (correlations.find(([c, _, __]) => c == 1)) {
237
+ result = stylingIntersect(correlations, stylings, current, id)
238
+ } else if (correlations.find(([c, _, __]) => c == 0)) {
239
+ result = stylingSplice(correlations, stylings, current, id)
240
+ } else if (
241
+ correlations.find(([c, _, __]) => c == 2) ||
242
+ correlations.length == 0
243
+ ) {
244
+ result = [...stylings, current]
245
+ }
246
+
247
+ setText({
248
+ ...text,
249
+ stylings: result,
250
+ })
251
+ }
252
+
253
+ // Get stylings encompassing an index within it's range
254
+ const getIntersectStylings = (index, startOffset = 0, finishOffset = 0) => {
255
+ // Find all stylings with encompassing range
256
+ let matches = text.stylings.filter(
257
+ ({ start, finish }) =>
258
+ index >= start + startOffset && index < finish + finishOffset
259
+ )
260
+
261
+ return matches
262
+ }
263
+
264
+ // Get stylings after an index
265
+ const getSucceedStylings = (index) => {
266
+ // Find all stylings after the index
267
+ let matches = text.stylings.filter(({ start, finish }) => start >= index)
268
+
269
+ return matches
270
+ }
271
+
272
+ const getStylingIndex = (styling) => {
273
+ return text.stylings.findIndex(
274
+ (item) =>
275
+ item.start == styling.start &&
276
+ item.finish == styling.finish &&
277
+ item.type == styling.type
278
+ )
279
+ }
280
+
281
+ const onChange = (value) => {
282
+ let difference = value.length - text.content.length
283
+
284
+ let selection = field.current.selectionStart - difference
285
+
286
+ let stylings = text.stylings.slice()
287
+ let succeeding = getSucceedStylings(selection)
288
+ let changes = []
289
+
290
+ for (let succeed of succeeding) {
291
+ let index = getStylingIndex(succeed)
292
+ let styling = stylings[index]
293
+
294
+ changes.push([
295
+ index,
296
+ styling.start + difference,
297
+ styling.finish + difference,
298
+ ])
299
+ }
300
+
301
+ let intersecting = getIntersectStylings(selection, 1, 1)
302
+
303
+ for (let intersect of intersecting) {
304
+ let index = getStylingIndex(intersect)
305
+ let styling = stylings[index]
306
+
307
+ changes.push([index, styling.start, styling.finish + difference])
308
+ }
309
+
310
+ let spliced = []
311
+ for (let [index, start, finish] of changes) {
312
+ stylings[index] = {
313
+ ...stylings[index],
314
+ start: start,
315
+ finish: finish,
316
+ }
317
+
318
+ if (start >= finish) spliced.push(index)
319
+ }
320
+
321
+ stylings = stylings.filter((_, index) => !spliced.includes(index))
322
+
323
+ setText({ ...text, content: value, stylings: stylings })
324
+ }
325
+
326
+ const getContent = () => {
327
+ let content = text.content
328
+
329
+ // Get all styling indices
330
+ let indices = text.stylings
331
+ .map(({ start, finish }) => [start, finish])
332
+ .flat()
333
+
334
+ // Sort ascendingly
335
+ indices = indices.sort((a, b) => a - b)
336
+
337
+ // Remove duplicates
338
+ indices = indices.filter(
339
+ (element, index) => indices.indexOf(element) == index
340
+ )
341
+
342
+ // Add first index if not present
343
+ if (indices[0] != 0) indices.unshift(0)
344
+
345
+ // Add last index if not present
346
+ let last = text.content.length
347
+ if (indices[indices.length - 1] != last) indices.push(last)
348
+
349
+ let result = []
350
+
351
+ for (let i = 0; i < indices.length - 1; i++) {
352
+ result.push([indices[i], content.substring(indices[i], indices[i + 1])])
353
+ }
354
+
355
+ return result
356
+ }
357
+
358
+ const getFieldSelection = () => {
359
+ if (document.activeElement != field.current) return [0, 0]
360
+
361
+ let selection = window.getSelection()
362
+ let start
363
+ let end
364
+
365
+ if (selection.rangeCount) {
366
+ let range = selection.getRangeAt(0)
367
+ start = range.startOffset
368
+ end = range.endOffset
369
+ }
370
+
371
+ return [start, end]
372
+ }
373
+
374
+ return (
375
+ <div className="align-center box-border flex h-min w-[400px] flex-col items-center justify-center rounded bg-gray-300 shadow-md">
376
+ <div className={clsx("flex w-full flex-row justify-start p-2")}>
377
+ <Property
378
+ name="B"
379
+ onMouseDown={(event) => {
380
+ event.preventDefault() // This does not take focus away from field which allows the function to retrieve the current selection data
381
+ perform("bold")
382
+ }}
383
+ />
384
+ <Property
385
+ name="I"
386
+ onClick={() => {
387
+ perform("italic")
388
+ }}
389
+ />
390
+ <Property
391
+ name="U"
392
+ onClick={() => {
393
+ perform("under")
394
+ }}
395
+ />
396
+ <Property
397
+ name="S"
398
+ onClick={() => {
399
+ perform("strike")
400
+ }}
401
+ />
402
+ </div>
403
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
404
+ <div className="w-full">
405
+ <div
406
+ ref={field}
407
+ contentEditable="true"
408
+ dangerouslySetInnerHTML={{
409
+ __html: getContent()
410
+ .map((_data, index) => {
411
+ let [start, data] = _data
412
+
413
+ // Get stylings encompassing an index within it's range
414
+ let stylings = getIntersectStylings(start)
415
+
416
+ return `<span class="${stylings
417
+ .map((styling) => styleClasses[styling.type])
418
+ .join("")}">${data}</span>`
419
+ // return `<span
420
+ // class="${stylings
421
+ // .map((styling) => styleClasses[styling.type])
422
+ // .join(" ")}"
423
+
424
+ // >
425
+ // ${data}
426
+ // </span>`
427
+ })
428
+ .join(" "),
429
+ }}
430
+ className="h-[150px] w-full resize-none border-none p-2 outline-none"
431
+ onInput={(event) => {
432
+ let target: any = event.target
433
+
434
+ let [start, end] = getFieldSelection()
435
+
436
+ // console.log(target)
437
+ // console.log(target.selectionStart)
438
+ // console.log(target.selectionEnd)
439
+
440
+ setText({
441
+ ...text,
442
+ content: target.textContent,
443
+ revert: [start, end],
444
+ })
445
+ }}
446
+ ></div>
447
+
448
+ {/* <RichTextarea
449
+ ref={field}
450
+ value={text.content}
451
+ style={{ width: "100%" }} // tailwind w-full does not work
452
+ className="order-none h-[150px] resize-none p-2 outline-none"
453
+ onChange={(e) => {
454
+ onChange(e.target.value)
455
+ }}
456
+ >
457
+ {(value) => {
458
+ // Get all styling indices
459
+ let indices = text.stylings
460
+ .map(({ start, finish }) => [start, finish])
461
+ .flat()
462
+
463
+ // Sort ascendingly
464
+ indices = indices.sort((a, b) => a - b)
465
+
466
+ // Remove duplicates
467
+ indices = indices.filter(
468
+ (element, index) => indices.indexOf(element) == index
469
+ )
470
+
471
+ // Add first index if not present
472
+ if (indices[0] != 0) indices.unshift(0)
473
+
474
+ // Add last index if not present
475
+ let last = text.content.length
476
+ if (indices[indices.length - 1] != last) indices.push(last)
477
+
478
+ let result = []
479
+
480
+ for (let i = 0; i < indices.length - 1; i++) {
481
+ result.push([
482
+ indices[i],
483
+ value.substring(indices[i], indices[i + 1]),
484
+ ])
485
+ }
486
+
487
+ return result.map((_data, index) => {
488
+ let [start, data] = _data
489
+
490
+ // Get stylings encompassing an index within it's range
491
+ let stylings = getIntersectStylings(start)
492
+
493
+ return (
494
+ <span
495
+ key={index}
496
+ className={`${stylings
497
+ .map((styling) => styleClasses[styling.type])
498
+ .join(" ")}
499
+ `}
500
+ >
501
+ {data}
502
+ </span>
503
+ )
504
+ })
505
+ }}
506
+ </RichTextarea> */}
507
+ </div>
508
+ <div className="h-[1px] w-full bg-slate-600">&nbsp;</div>
509
+ <button className="my-1 rounded bg-cyan-800 p-2 py-1 text-white">
510
+ Submit
511
+ </button>
512
+ </div>
513
+ )
514
+ }