@startupjs-ui/rank 0.1.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
7
+
8
+ **Note:** Version bump only for package @startupjs-ui/rank
9
+
10
+
11
+
12
+
13
+
14
+ ## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
15
+
16
+
17
+ ### Features
18
+
19
+ * add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
20
+ * **rank:** refactor Rank component ([f38b0df](https://github.com/startupjs/startupjs-ui/commit/f38b0dfb212fa77efe1fa0e52b2b60d1ff4f7c95))
package/README.mdx ADDED
@@ -0,0 +1,70 @@
1
+ import { useState } from 'react'
2
+ import Rank, { _PropsJsonSchema as RankPropsJsonSchema } from './index'
3
+ import { Sandbox } from '@startupjs-ui/docs'
4
+
5
+ # Rank
6
+
7
+ Rank input allows user to arrange options in specific order.
8
+
9
+ ```jsx
10
+ import { Rank } from 'startupjs-ui'
11
+ ```
12
+
13
+ ## Simple example
14
+
15
+ ```jsx example
16
+ const options = ['option1', 'option2', 'option3', 'option4', 'option5']
17
+ const [value, setValue] = useState(options)
18
+
19
+ return (
20
+ <Rank
21
+ value={value}
22
+ options={options}
23
+ onChange={setValue}
24
+ />
25
+ )
26
+ ```
27
+
28
+ ## Disabled
29
+
30
+ ```jsx example
31
+ const options = ['option1', 'option2', 'option3', 'option4', 'option5']
32
+ const [value, setValue] = useState(options)
33
+
34
+ return (
35
+ <Rank
36
+ disabled
37
+ value={value}
38
+ options={options}
39
+ onChange={setValue}
40
+ />
41
+ )
42
+ ```
43
+
44
+ ## Readonly
45
+
46
+ ```jsx example
47
+ const options = ['option1', 'option2', 'option3', 'option4', 'option5']
48
+ const [value, setValue] = useState(options)
49
+
50
+ return (
51
+ <Rank
52
+ readonly
53
+ value={value}
54
+ options={options}
55
+ onChange={setValue}
56
+ />
57
+ )
58
+ ```
59
+
60
+ ## Sandbox
61
+
62
+ <Sandbox
63
+ Component={Rank}
64
+ propsJsonSchema={RankPropsJsonSchema}
65
+ props={{
66
+ options: ['option1', 'option2', 'option3', 'option4', 'option5'],
67
+ onChange: value => alert('New order: ' + JSON.stringify(value))
68
+ }}
69
+ block
70
+ />
package/helpers.ts ADDED
@@ -0,0 +1,25 @@
1
+ export function stringifyValue (option: any): string {
2
+ try {
3
+ const value = getOptionValue(option)
4
+ return JSON.stringify(value)
5
+ } catch (err) {
6
+ console.error(err)
7
+ return ''
8
+ }
9
+ }
10
+
11
+ export function getOptionValue (option: any): any {
12
+ return option?.value ?? option
13
+ }
14
+
15
+ export function getOptionLabel (option: any): any {
16
+ return option?.label ?? option
17
+ }
18
+
19
+ export function move<T> (arr: T[], oldIndex: number, newIndex: number): T[] {
20
+ const arrCopy = arr.slice()
21
+ const element = arrCopy[oldIndex]
22
+ arrCopy.splice(oldIndex, 1)
23
+ arrCopy.splice(newIndex, 0, element)
24
+ return arrCopy
25
+ }
@@ -0,0 +1,42 @@
1
+ .droppable
2
+ margin-top 1u
3
+
4
+ .droppable
5
+ .span
6
+ flex-shrink 1
7
+ flex-grow 1
8
+
9
+ .readonly
10
+ &-index
11
+ min-width 2.5u
12
+ &-text
13
+ flex-shrink 1
14
+ flex-grow 1
15
+
16
+ .draggable
17
+ padding 1u
18
+ border-width 1px
19
+ radius()
20
+ margin-top 1u
21
+
22
+ .cursor
23
+ +web()
24
+ cursor pointer
25
+
26
+ .span
27
+ margin-left 2.5u
28
+ justify-content center
29
+
30
+ .right
31
+ flex-shrink 0
32
+ justify-content center
33
+ margin-left 4u
34
+
35
+ .icon
36
+ &.disabled
37
+ color var(--color-text-subtle)
38
+
39
+ .hint
40
+ font(caption)
41
+ margin-top 1u
42
+
package/index.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import { type StyleProp, type ViewStyle } from 'react-native';
5
+ export declare const _PropsJsonSchema: {};
6
+ export interface RankProps {
7
+ /** Custom styles applied to the root view */
8
+ style?: StyleProp<ViewStyle>;
9
+ /** Options list (strings, numbers, or `{ value, label }` objects) @default [] */
10
+ options?: any[];
11
+ /** Current order as a list of option values @default [] */
12
+ value?: any[];
13
+ /** Disable drag-and-drop interactions @default false */
14
+ disabled?: boolean;
15
+ /** Render a non-interactive ranked list @default false */
16
+ readonly?: boolean;
17
+ /** Fired when order changes; receives array of option values */
18
+ onChange?: (value: any[]) => void;
19
+ }
20
+ declare const _default: import("react").ComponentType<RankProps>;
21
+ export default _default;
package/index.tsx ADDED
@@ -0,0 +1,162 @@
1
+ import { useMemo, type ReactNode } from 'react'
2
+ import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer, $ } from 'startupjs'
4
+ import { themed, useColors } from '@startupjs-ui/core'
5
+ import { DragDropProvider, Draggable, Droppable } from '@startupjs-ui/draggable'
6
+ import Div from '@startupjs-ui/div'
7
+ import Icon from '@startupjs-ui/icon'
8
+ import Select from '@startupjs-ui/select'
9
+ import Span from '@startupjs-ui/span'
10
+ import { faGripVertical } from '@fortawesome/free-solid-svg-icons/faGripVertical'
11
+ import { getOptionLabel, getOptionValue, stringifyValue, move } from './helpers'
12
+ import STYLES from './index.cssx.styl'
13
+
14
+ export const _PropsJsonSchema = {/* RankProps */} // used in docs generation
15
+
16
+ export interface RankProps {
17
+ /** Custom styles applied to the root view */
18
+ style?: StyleProp<ViewStyle>
19
+ /** Options list (strings, numbers, or `{ value, label }` objects) @default [] */
20
+ options?: any[]
21
+ /** Current order as a list of option values @default [] */
22
+ value?: any[]
23
+ /** Disable drag-and-drop interactions @default false */
24
+ disabled?: boolean
25
+ /** Render a non-interactive ranked list @default false */
26
+ readonly?: boolean
27
+ /** Fired when order changes; receives array of option values */
28
+ onChange?: (value: any[]) => void
29
+ }
30
+
31
+ function Rank (props: RankProps): ReactNode {
32
+ const {
33
+ options = [],
34
+ readonly = false,
35
+ value = [],
36
+ style
37
+ } = props
38
+
39
+ const sortedValue = useMemo(() => {
40
+ return options.slice().sort((a, b) => {
41
+ return value.findIndex(i => stringifyValue(i) === stringifyValue(a)) -
42
+ value.findIndex(i => stringifyValue(i) === stringifyValue(b))
43
+ })
44
+ // eslint-disable-next-line react-hooks/exhaustive-deps
45
+ }, [value.toString()])
46
+
47
+ return pug`
48
+ if readonly
49
+ RankReadonly(value=sortedValue style=style)
50
+ else
51
+ RankInput(...props value=sortedValue)
52
+ `
53
+ }
54
+
55
+ const RankInput = observer(function RankInput ({
56
+ value,
57
+ onChange,
58
+ disabled,
59
+ style
60
+ }: {
61
+ value: any[]
62
+ onChange?: (value: any[]) => void
63
+ disabled?: boolean
64
+ style?: StyleProp<ViewStyle>
65
+ }): ReactNode {
66
+ const $width: any = $()
67
+ const dropId = useMemo(() => $.id(), [])
68
+
69
+ const getColor = useColors()
70
+
71
+ const selectOptions = useMemo(() => {
72
+ return value.map((_o, i) => ({ label: i + 1, value: i }))
73
+ }, [value])
74
+
75
+ function onLayout (event: any) {
76
+ $width.set(event.nativeEvent.layout.width)
77
+ }
78
+
79
+ function onMoveItem (oldIndex: number, newIndex: number) {
80
+ const newItems = move(value, oldIndex, newIndex)
81
+ onChange && onChange(newItems.map(i => getOptionValue(i)))
82
+ }
83
+
84
+ function onDragEnd ({ dragId, hoverIndex }: { dragId: string, hoverIndex: number }) {
85
+ const oldIndex = value.findIndex(item => stringifyValue(item) === dragId)
86
+ const newIndex = hoverIndex < oldIndex ? hoverIndex : hoverIndex - 1
87
+ onMoveItem(oldIndex, newIndex)
88
+ }
89
+
90
+ function renderDragItem (item: any, index: number): ReactNode {
91
+ const dragId = stringifyValue(item)
92
+
93
+ function onSelectChange (newIndex: any) {
94
+ onMoveItem(index, newIndex)
95
+ }
96
+
97
+ // HACK: Draggable component has some visual bugs if styles are not passed
98
+ // through style object
99
+ const extraStyle: any = disabled ? { backgroundColor: getColor('bg-main-subtle') } : STYLES.cursor
100
+ const itemStyle: any = [
101
+ { ...STYLES.draggable, backgroundColor: getColor('bg-main-strong'), borderColor: getColor('border-main') },
102
+ { width: $width.get() },
103
+ extraStyle
104
+ ]
105
+
106
+ const Container: any = disabled ? Div : Draggable
107
+
108
+ return pug`
109
+ Container(
110
+ style=StyleSheet.flatten(itemStyle)
111
+ key=dragId
112
+ dragId=dragId
113
+ onDragEnd=onDragEnd
114
+ )
115
+ Div(row)
116
+ Select(
117
+ size='s'
118
+ disabled=disabled
119
+ showEmptyValue=false
120
+ options=selectOptions
121
+ value=index
122
+ onChange=onSelectChange
123
+ )
124
+ Div.span
125
+ Span= getOptionLabel(item)
126
+ Div.right
127
+ Icon.icon(icon=faGripVertical styleName={ disabled })
128
+ `
129
+ }
130
+
131
+ return pug`
132
+ Div(
133
+ style=style
134
+ onLayout=onLayout
135
+ )
136
+ Span.hint(italic) To rank the listed items drag and drop each item
137
+ DragDropProvider
138
+ Droppable.droppable(dropId=dropId)
139
+ each option, index in value
140
+ = renderDragItem(option, index)
141
+ `
142
+ })
143
+
144
+ const RankReadonly = observer(function RankReadonly ({
145
+ value,
146
+ style
147
+ }: {
148
+ value: any[]
149
+ style?: StyleProp<ViewStyle>
150
+ }): ReactNode {
151
+ return pug`
152
+ Div(style=style)
153
+ each option, index in value
154
+ Div.readonly(key=index row)
155
+ Div.readonly-index
156
+ Span #{index + 1}.&nbsp;
157
+ Div.readonly-text
158
+ Span= getOptionLabel(option)
159
+ `
160
+ })
161
+
162
+ export default observer(themed('Rank', Rank))
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@startupjs-ui/rank",
3
+ "version": "0.1.3",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "main": "index.tsx",
8
+ "types": "index.d.ts",
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@startupjs-ui/core": "^0.1.3",
12
+ "@startupjs-ui/div": "^0.1.3",
13
+ "@startupjs-ui/draggable": "^0.1.3",
14
+ "@startupjs-ui/icon": "^0.1.3",
15
+ "@startupjs-ui/select": "^0.1.3",
16
+ "@startupjs-ui/span": "^0.1.3"
17
+ },
18
+ "peerDependencies": {
19
+ "react": "*",
20
+ "react-native": "*",
21
+ "startupjs": "*"
22
+ },
23
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
24
+ }