@startupjs-ui/menu 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/menu
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
+ * **menu:** refactor Menu component. Fix useColors types. ([ffd4671](https://github.com/startupjs/startupjs-ui/commit/ffd46719c308f99d856d8020ebfc070bb479ab28))
@@ -0,0 +1,28 @@
1
+ _borderSize = 2px
2
+
3
+ .border
4
+ position absolute
5
+
6
+ &.top
7
+ &.bottom
8
+ left 0
9
+ right 0
10
+ height _borderSize
11
+
12
+ &.top
13
+ top 0
14
+
15
+ &.bottom
16
+ bottom 0
17
+
18
+ &.left
19
+ &.right
20
+ top 0
21
+ bottom 0
22
+ width _borderSize
23
+
24
+ &.left
25
+ left 0
26
+
27
+ &.right
28
+ right 0
@@ -0,0 +1,83 @@
1
+ import { useContext, type ReactNode } from 'react'
2
+ import { type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer } from 'startupjs'
4
+ import { themed, useColors } from '@startupjs-ui/core'
5
+ import Div from '@startupjs-ui/div'
6
+ import Icon, { type IconProps } from '@startupjs-ui/icon'
7
+ import Item, { type ItemProps } from '@startupjs-ui/item'
8
+ import Span from '@startupjs-ui/span'
9
+ import MenuContext from '../context'
10
+ import './index.cssx.styl'
11
+
12
+ export const _PropsJsonSchema = {/* MenuItemProps */} // used in docs generation
13
+
14
+ export interface MenuItemProps extends Omit<ItemProps, 'style' | 'onPress' | 'icon' | 'to'> {
15
+ /** Custom styles applied to the content wrapper */
16
+ containerStyle?: StyleProp<ViewStyle>
17
+ /** Content rendered inside the item */
18
+ children?: ReactNode
19
+ /** Highlight item as active @default false */
20
+ active?: boolean
21
+ /** Active border position @default 'none' */
22
+ activeBorder?: 'top' | 'bottom' | 'left' | 'right' | 'none'
23
+ /** Color applied to active item text, icon and border */
24
+ activeColor?: string
25
+ /** Render text in bold */
26
+ bold?: boolean
27
+ /** Icon displayed alongside the label */
28
+ icon?: IconProps['icon']
29
+ /** Icon position relative to the label @default 'left' */
30
+ iconPosition?: 'left' | 'right'
31
+ /** Text/icon color override */
32
+ color?: string
33
+ /** Navigation target passed to underlying Item */
34
+ to?: string
35
+ /** Handler called on item press */
36
+ onPress?: ItemProps['onPress']
37
+ }
38
+
39
+ function MenuItem ({
40
+ children,
41
+ containerStyle,
42
+ active = false,
43
+ bold,
44
+ icon,
45
+ ...props
46
+ }: MenuItemProps): ReactNode {
47
+ const context = useContext(MenuContext)
48
+ const getColor = useColors()
49
+
50
+ // TODO
51
+ // we should think about a better api
52
+ // and remove color, activeColor, activeBorder props
53
+ let color: string | undefined = props.color ?? context.color
54
+ color = getColor(color) ?? color
55
+ let activeColor = props.activeColor ?? context.activeColor
56
+ activeColor = getColor(activeColor) ?? activeColor
57
+ const activeBorder = props.activeBorder ?? context.activeBorder ?? 'none'
58
+ const iconPosition = props.iconPosition ?? context.iconPosition ?? 'left'
59
+
60
+ // TODO: prevent click if already active (for link and for div)
61
+ color = active ? (activeColor ?? getColor('text-primary')) : (color ?? getColor('text-main'))
62
+ const borderStyle: StyleProp<ViewStyle> = { backgroundColor: activeColor ?? getColor('border-primary') }
63
+
64
+ return pug`
65
+ Div
66
+ Item(...props)
67
+ if icon && iconPosition === 'left'
68
+ Item.Left
69
+ Icon(icon=icon style={ color })
70
+
71
+ Item.Content(style=[containerStyle])
72
+ Span(bold=bold style={ color })= children
73
+
74
+ if icon && iconPosition === 'right'
75
+ Item.Right
76
+ Icon(icon=icon style={ color })
77
+
78
+ if activeBorder !== 'none' && active
79
+ Div.border(styleName=[activeBorder] style=borderStyle)
80
+ `
81
+ }
82
+
83
+ export default observer(themed('MenuItem', MenuItem))
package/README.mdx ADDED
@@ -0,0 +1,311 @@
1
+ import { useState } from 'react'
2
+ import { pug, u } from 'startupjs'
3
+ import useRouter from 'startupjs/useRouter'
4
+ import { faShareAlt, faHouseUser, faTable } from '@fortawesome/free-solid-svg-icons'
5
+ import Menu, { _PropsJsonSchema as MenuPropsJsonSchema } from './index'
6
+ import MenuItem, { _PropsJsonSchema as MenuItemPropsJsonSchema } from './MenuItem'
7
+ import Collapse from '@startupjs-ui/collapse'
8
+ import { Sandbox } from '@startupjs-ui/docs'
9
+
10
+ # Menu
11
+
12
+ Inherits [Div props](/docs/components/Div).
13
+
14
+ Menu displays a list of choices and also can be used for navigation.
15
+
16
+ ```jsx
17
+ import { Menu } from 'startupjs-ui'
18
+ ```
19
+
20
+ ## Simple example
21
+
22
+ ```jsx example
23
+ const [active, setActive] = useState('option-1')
24
+ return (
25
+ <Menu>
26
+ <Menu.Item
27
+ active={active === 'option-1'}
28
+ onPress={() => setActive('option-1')}
29
+ >
30
+ Option-1
31
+ </Menu.Item>
32
+ <Menu.Item
33
+ active={active === 'option-2'}
34
+ onPress={() => setActive('option-2')}
35
+ >
36
+ Option-2
37
+ </Menu.Item>
38
+ </Menu>
39
+ )
40
+ ```
41
+
42
+ ## Highlighted active item
43
+
44
+ By default the active menu item is not highlighted. To highlight it use the `activeBorder` property.
45
+
46
+ ```jsx example
47
+ const [active, setActive] = useState('option-1')
48
+ return (
49
+ <Menu activeBorder='left'>
50
+ <Menu.Item
51
+ active={active === 'option-1'}
52
+ onPress={() => setActive('option-1')}
53
+ >
54
+ Option-1
55
+ </Menu.Item>
56
+ <Menu.Item
57
+ active={active === 'option-2'}
58
+ onPress={() => setActive('option-2')}
59
+ >
60
+ Option-2
61
+ </Menu.Item>
62
+ <Menu.Item
63
+ active={active === 'option-3'}
64
+ onPress={() => setActive('option-3')}
65
+ >
66
+ Option-3
67
+ </Menu.Item>
68
+ </Menu>
69
+ )
70
+ ```
71
+
72
+ ## Horizontal menu
73
+
74
+ By default the menu shows vertically. To make it horizontal, you need pass the string `horizontal` to the `variant` property.
75
+
76
+ ```jsx example
77
+ const [active, setActive] = useState('option-1')
78
+ return (
79
+ <Menu variant='horizontal'>
80
+ <Menu.Item
81
+ active={active === 'option-1'}
82
+ onPress={() => setActive('option-1')}
83
+ >
84
+ Option-1
85
+ </Menu.Item>
86
+ <Menu.Item
87
+ active={active === 'option-2'}
88
+ onPress={() => setActive('option-2')}
89
+ >
90
+ Option-2
91
+ </Menu.Item>
92
+ <Menu.Item
93
+ active={active === 'option-3'}
94
+ onPress={() => setActive('option-3')}
95
+ >
96
+ Option-3
97
+ </Menu.Item>
98
+ </Menu>
99
+ )
100
+ ```
101
+
102
+ ## Menu item icon
103
+
104
+ To display icon in menu item pass the `icon` property to it. Position of icon can be changed by passing `iconPosition` to `Menu` component (`left` by default) and can be override in menu item.
105
+
106
+ ```jsx example
107
+ const [active, setActive] = useState('option-1')
108
+ return (
109
+ <Menu variant='horizontal'>
110
+ <Menu.Item
111
+ active={active === 'option-1'}
112
+ icon={faShareAlt}
113
+ onPress={() => setActive('option-1')}
114
+ >
115
+ Option-1
116
+ </Menu.Item>
117
+ <Menu.Item
118
+ active={active === 'option-2'}
119
+ icon={faHouseUser}
120
+ onPress={() => setActive('option-2')}
121
+ >
122
+ Option-2
123
+ </Menu.Item>
124
+ <Menu.Item
125
+ active={active === 'option-3'}
126
+ icon={faTable}
127
+ onPress={() => setActive('option-3')}
128
+ >
129
+ Option-3
130
+ </Menu.Item>
131
+ </Menu>
132
+ )
133
+ ```
134
+
135
+ ## Change color of active item
136
+
137
+ By default, the active menu item has a `primary` color. Use the `activeColor` property to set the desired color for the active element. This property will also change the color of border and icon (if you passed an `activeBorder` property for a Menu or an `icon` for a Menu.Item). `activeColor` accepts color as a string which will be compatible with the CSS color property.(ex. `red`, `#fff`, `rgb(123, 23, 122)`)
138
+
139
+ ```jsx example
140
+ const [active, setActive] = useState('option-1')
141
+ return (
142
+ <Menu
143
+ activeBorder='bottom'
144
+ variant='horizontal'
145
+ activeColor='rgb(123, 23, 122)'
146
+ >
147
+ <Menu.Item
148
+ active={active === 'option-1'}
149
+ icon={faShareAlt}
150
+ onPress={() => setActive('option-1')}
151
+ >
152
+ Option-1
153
+ </Menu.Item>
154
+ <Menu.Item
155
+ active={active === 'option-2'}
156
+ icon={faHouseUser}
157
+ onPress={() => setActive('option-2')}
158
+ >
159
+ Option-2
160
+ </Menu.Item>
161
+ <Menu.Item
162
+ active={active === 'option-3'}
163
+ icon={faTable}
164
+ onPress={() => setActive('option-3')}
165
+ >
166
+ Option-3
167
+ </Menu.Item>
168
+ </Menu>
169
+ )
170
+ ```
171
+
172
+ ## Menu item as link
173
+
174
+ Sometimes you want using menu item as link, for example in navigation menu. See below how you can do this.
175
+
176
+ ```jsx example
177
+ const { usePathname } = useRouter()
178
+ const url = usePathname()
179
+ const components = [
180
+ { label: 'Menu', to: '/docs/Menu' },
181
+ { label: 'Div', to: '/docs/Div' },
182
+ { label: 'Button', to: '/docs/Button' }
183
+ ]
184
+ return pug`
185
+ Menu(activeBorder='left')
186
+ each component, index in components
187
+ - const to = component.to
188
+ Menu.Item(
189
+ key=index
190
+ active=to === url
191
+ to=to
192
+ )= component.label
193
+ `
194
+ ```
195
+
196
+ ## Complex interaction (integration with other components)
197
+
198
+ An example of a collapsible menu with submenus nested in.
199
+
200
+ ```jsx example
201
+ const [showMenu, setShowMenu] = useState(true)
202
+ const [showSubMenu, setShowSubMenu] = useState(false)
203
+ const [active, setActive] = useState('option-1')
204
+ return (
205
+ <Collapse
206
+ variant='pure'
207
+ open={showMenu}
208
+ onChange={() => setShowMenu(!showMenu)}
209
+ >
210
+ <Collapse.Header>
211
+ <Menu.Item
212
+ style={{ paddingLeft: 0 }}
213
+ active={showMenu}
214
+ >
215
+ Main menu
216
+ </Menu.Item>
217
+ </Collapse.Header>
218
+ <Collapse.Content>
219
+ <Menu activeBorder='left'>
220
+ <Menu.Item
221
+ active={active === 'option-1'}
222
+ onPress={() => setActive('option-1')}
223
+ >
224
+ Option-1
225
+ </Menu.Item>
226
+ <Menu.Item
227
+ active={active === 'option-2'}
228
+ onPress={() => setActive('option-2')}
229
+ >
230
+ Option-2
231
+ </Menu.Item>
232
+ <Collapse
233
+ style={{ marginLeft: u(2) }}
234
+ variant='pure'
235
+ open={showSubMenu}
236
+ onChange={() => setShowSubMenu(!showSubMenu)}
237
+ >
238
+ <Collapse.Header>
239
+ <Menu.Item
240
+ style={{ paddingLeft: 0 }}
241
+ active={showSubMenu}
242
+ >
243
+ Option-3
244
+ </Menu.Item>
245
+ </Collapse.Header>
246
+ <Collapse.Content>
247
+ <Menu activeBorder='left'>
248
+ <Menu.Item
249
+ active={active === 'option-4'}
250
+ onPress={() => setActive('option-4')}
251
+ >
252
+ Option-4
253
+ </Menu.Item>
254
+ <Menu.Item
255
+ active={active === 'option-5'}
256
+ onPress={() => setActive('option-5')}
257
+ >
258
+ Option-5
259
+ </Menu.Item>
260
+ </Menu>
261
+ </Collapse.Content>
262
+ </Collapse>
263
+ </Menu>
264
+ </Collapse.Content>
265
+ </Collapse>
266
+ )
267
+ ```
268
+
269
+ ## Sandbox
270
+
271
+ ### Menu
272
+
273
+ <Sandbox
274
+ Component={Menu}
275
+ propsJsonSchema={MenuPropsJsonSchema}
276
+ props={{
277
+ children: [
278
+ <MenuItem
279
+ key='option-1'
280
+ icon={faHouseUser}
281
+ active={true}
282
+ onPress={() => alert('"onPress" event on "Option-1"')}
283
+ >
284
+ Option-1
285
+ </MenuItem>,
286
+ <MenuItem
287
+ key='option-2'
288
+ active={false}
289
+ onPress={() => alert('"onPress" event on "Option-2"')}
290
+ >
291
+ Option-2
292
+ </MenuItem>
293
+ ]
294
+ }}
295
+ />
296
+
297
+ ### Menu.Item
298
+
299
+ <Sandbox
300
+ Component={Menu.Item}
301
+ propsJsonSchema={MenuItemPropsJsonSchema}
302
+ extraParams={{
303
+ icon: {
304
+ showIconSelect: true
305
+ }
306
+ }}
307
+ props={{
308
+ children: 'Option-1',
309
+ onPress: () => alert('"onPress" event on "Option-1"')
310
+ }}
311
+ />
package/context.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { createContext } from 'react'
2
+
3
+ export interface MenuContextValue {
4
+ activeBorder?: 'top' | 'bottom' | 'left' | 'right' | 'none'
5
+ activeColor?: string
6
+ iconPosition?: 'left' | 'right'
7
+ color?: string
8
+ }
9
+
10
+ export default createContext<MenuContextValue>({})
@@ -0,0 +1,3 @@
1
+ .root
2
+ &.horizontal
3
+ flex-direction row
package/index.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import { type ReactNode } from 'react';
5
+ import { type StyleProp, type ViewStyle } from 'react-native';
6
+ import { type DivProps } from '@startupjs-ui/div';
7
+ import './index.cssx.styl';
8
+ export declare const _PropsJsonSchema: {};
9
+ export interface MenuProps extends Omit<DivProps, 'variant' | 'style'> {
10
+ /** Custom styles applied to the root view */
11
+ style?: StyleProp<ViewStyle>;
12
+ /** Content rendered inside Menu */
13
+ children?: ReactNode;
14
+ /** Active border position passed to Menu items */
15
+ activeBorder?: 'top' | 'bottom' | 'left' | 'right' | 'none';
16
+ /** Color applied to active Menu items */
17
+ activeColor?: string;
18
+ /** Icon position applied to nested Menu items @default 'left' */
19
+ iconPosition?: 'left' | 'right';
20
+ /** Layout orientation @default 'vertical' */
21
+ variant?: 'vertical' | 'horizontal';
22
+ }
23
+ declare const ObservedMenu: any;
24
+ export { default as MenuItem } from './MenuItem';
25
+ export default ObservedMenu;
package/index.tsx ADDED
@@ -0,0 +1,55 @@
1
+ import { useMemo, type ReactNode } from 'react'
2
+ import { type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer } from 'startupjs'
4
+ import { themed } from '@startupjs-ui/core'
5
+ import Div, { type DivProps } from '@startupjs-ui/div'
6
+ import MenuItem from './MenuItem'
7
+ import Context, { type MenuContextValue } from './context'
8
+ import './index.cssx.styl'
9
+
10
+ export const _PropsJsonSchema = {/* MenuProps */} // used in docs generation
11
+
12
+ export interface MenuProps extends Omit<DivProps, 'variant' | 'style'> {
13
+ /** Custom styles applied to the root view */
14
+ style?: StyleProp<ViewStyle>
15
+ /** Content rendered inside Menu */
16
+ children?: ReactNode
17
+ /** Active border position passed to Menu items */
18
+ activeBorder?: 'top' | 'bottom' | 'left' | 'right' | 'none'
19
+ /** Color applied to active Menu items */
20
+ activeColor?: string
21
+ /** Icon position applied to nested Menu items @default 'left' */
22
+ iconPosition?: 'left' | 'right'
23
+ /** Layout orientation @default 'vertical' */
24
+ variant?: 'vertical' | 'horizontal'
25
+ }
26
+
27
+ function Menu ({
28
+ style,
29
+ children,
30
+ variant = 'vertical',
31
+ activeBorder,
32
+ iconPosition,
33
+ activeColor,
34
+ ...props
35
+ }: MenuProps): ReactNode {
36
+ const value = useMemo<MenuContextValue>(() => {
37
+ return { activeBorder, activeColor, iconPosition }
38
+ }, [activeBorder, activeColor, iconPosition])
39
+
40
+ return pug`
41
+ Context.Provider(value=value)
42
+ Div.root(
43
+ style=style
44
+ styleName=[variant]
45
+ ...props
46
+ )= children
47
+ `
48
+ }
49
+
50
+ const ObservedMenu: any = observer(themed('Menu', Menu))
51
+
52
+ ObservedMenu.Item = MenuItem
53
+
54
+ export { default as MenuItem } from './MenuItem'
55
+ export default ObservedMenu
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@startupjs-ui/menu",
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/icon": "^0.1.3",
14
+ "@startupjs-ui/item": "^0.1.3",
15
+ "@startupjs-ui/span": "^0.1.3"
16
+ },
17
+ "peerDependencies": {
18
+ "react": "*",
19
+ "react-native": "*",
20
+ "startupjs": "*"
21
+ },
22
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
23
+ }