@startupjs-ui/collapse 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,25 @@
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/collapse
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
+ ### Bug Fixes
18
+
19
+ * **collapse:** remove re-exports of subcomponents ([fd521e6](https://github.com/startupjs/startupjs-ui/commit/fd521e6c3509553c8ad231de09933a987b0c74f9))
20
+
21
+
22
+ ### Features
23
+
24
+ * 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))
25
+ * **collapse:** refactor Collapse component ([f43d6e5](https://github.com/startupjs/startupjs-ui/commit/f43d6e55cba1dd9d970125016a1069671a4e581b))
@@ -0,0 +1,7 @@
1
+ .root
2
+ &.full
3
+ // NOTE
4
+ // Collapse with text is almost never used.
5
+ // Developers pass its own components to children.
6
+ // Therefore, does the content need paddings ?
7
+ padding 0 2u 2u
@@ -0,0 +1,57 @@
1
+ import React, { type ReactNode } from 'react'
2
+ import Collapsible from 'react-native-collapsible'
3
+ import { pug, observer } from 'startupjs'
4
+ import { themed } from '@startupjs-ui/core'
5
+ import Span from '@startupjs-ui/span'
6
+ import './index.cssx.styl'
7
+
8
+ export const _PropsJsonSchema = {/* CollapseContentProps */}
9
+
10
+ export interface CollapseContentProps {
11
+ /** Custom styles applied to the collapsible container */
12
+ style?: any
13
+ /** Content rendered inside the collapsible area */
14
+ children?: ReactNode
15
+ /** Whether the collapse is open (provided internally) */
16
+ open?: boolean
17
+ /** Collapse variant controlling paddings */
18
+ variant?: 'full' | 'pure'
19
+ /** Alignment of collapsed content */
20
+ align?: 'top' | 'center' | 'bottom'
21
+ /** Height when collapsed */
22
+ collapsedHeight?: number
23
+ /** Enable pointer events while collapsed */
24
+ enablePointerEvents?: boolean
25
+ /** Animation duration in ms */
26
+ duration?: number
27
+ /** Easing function or preset name */
28
+ easing?: ((value: number) => number) | string
29
+ /** Render children even when collapsed */
30
+ renderChildrenCollapsed?: boolean
31
+ /** Callback fired after collapse animation ends */
32
+ onAnimationEnd?: () => void
33
+ }
34
+
35
+ function CollapseContent ({
36
+ style,
37
+ children,
38
+ open,
39
+ variant,
40
+ ...props
41
+ }: CollapseContentProps): ReactNode {
42
+ const content = React.Children.map(children as any, (child: any, index: number): ReactNode => {
43
+ if (typeof child === 'string') {
44
+ return pug`
45
+ Span(key=index)= child
46
+ `
47
+ }
48
+ return child
49
+ })
50
+
51
+ return pug`
52
+ Collapsible.root(style=style styleName=[variant] collapsed=!open ...props)
53
+ = content
54
+ `
55
+ }
56
+
57
+ export default observer(themed('CollapseContent', CollapseContent))
@@ -0,0 +1,13 @@
1
+ _gutter = $UI.gutters.m
2
+
3
+ .root
4
+ align-items center
5
+ &.full
6
+ padding _gutter
7
+
8
+ .container
9
+ flex 1
10
+ margin-left 1u
11
+ &.reverse
12
+ margin-right 1u
13
+ margin-left 0
@@ -0,0 +1,90 @@
1
+ import React, { useRef, type ReactNode } from 'react'
2
+ import { Animated, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer, useDidUpdate } from 'startupjs'
4
+ import { themed } from '@startupjs-ui/core'
5
+ import Div from '@startupjs-ui/div'
6
+ import Icon, { type IconProps } from '@startupjs-ui/icon'
7
+ import Span from '@startupjs-ui/span'
8
+ import { faCaretRight } from '@fortawesome/free-solid-svg-icons/faCaretRight'
9
+ import './index.cssx.styl'
10
+
11
+ export const _PropsJsonSchema = {/* CollapseHeaderProps */}
12
+
13
+ export interface CollapseHeaderProps {
14
+ /** Custom styles applied to the root view */
15
+ style?: StyleProp<ViewStyle>
16
+ /** Custom styles applied to the icon */
17
+ iconStyle?: IconProps['style']
18
+ /** Custom styles applied to the content container */
19
+ containerStyle?: StyleProp<ViewStyle>
20
+ /** Header content */
21
+ children?: ReactNode
22
+ /** Collapse variant controlling paddings */
23
+ variant?: 'full' | 'pure'
24
+ /** Icon position relative to the title @default 'left' */
25
+ iconPosition?: 'left' | 'right'
26
+ /** Icon displayed in header; true uses default caret @default true */
27
+ icon?: IconProps['icon'] | boolean
28
+ /** Whether the collapse is open (provided internally) */
29
+ open?: boolean
30
+ /** Press handler provided by Collapse */
31
+ onPress?: () => void
32
+ }
33
+
34
+ function CollapseHeader ({
35
+ style,
36
+ iconStyle,
37
+ containerStyle,
38
+ children,
39
+ variant,
40
+ iconPosition = 'left',
41
+ icon = true,
42
+ open,
43
+ onPress,
44
+ ...props
45
+ }: CollapseHeaderProps): ReactNode {
46
+ if (icon === true) icon = faCaretRight
47
+ const reverse = iconPosition === 'right'
48
+ const animation = useRef(new Animated.Value(open ? 1 : 0)).current
49
+
50
+ useDidUpdate(() => {
51
+ Animated.timing(
52
+ animation,
53
+ {
54
+ toValue: open ? 1 : 0,
55
+ duration: 250,
56
+ useNativeDriver: true
57
+ }
58
+ ).start()
59
+ }, [open])
60
+
61
+ return pug`
62
+ Div.root(
63
+ row
64
+ style=style
65
+ styleName=[variant]
66
+ onPress=onPress
67
+ reverse=reverse
68
+ ...props
69
+ )
70
+ if icon
71
+ Animated.View(
72
+ style={
73
+ transform: [{
74
+ rotate: animation.interpolate({
75
+ inputRange: [0, 1],
76
+ outputRange: [reverse ? '180deg' : '0deg', '90deg']
77
+ })
78
+ }]
79
+ }
80
+ )
81
+ Icon(icon=icon style=iconStyle)
82
+ Div.container(style=containerStyle styleName={reverse})
83
+ if typeof children === 'string'
84
+ Span= children
85
+ else
86
+ = children
87
+ `
88
+ }
89
+
90
+ export default observer(themed('CollapseHeader', CollapseHeader))
package/README.mdx ADDED
@@ -0,0 +1,148 @@
1
+ import { $ } from 'startupjs'
2
+ import { useState } from 'react'
3
+ import { Sandbox } from '@startupjs-ui/docs'
4
+ import { faAngleRight } from '@fortawesome/free-solid-svg-icons'
5
+ import Collapse, { _PropsJsonSchema as CollapsePropsJsonSchema } from './index'
6
+ import { _PropsJsonSchema as CollapseHeaderPropsJsonSchema } from './CollapseHeader'
7
+ import { _PropsJsonSchema as CollapseContentPropsJsonSchema } from './CollapseContent'
8
+ import Div from '@startupjs-ui/div'
9
+
10
+ # Collapse
11
+
12
+ Inherits [react-native-collapsible](https://github.com/oblador/react-native-collapsible).
13
+
14
+ Collapse allows to toggle the visibility of content.
15
+
16
+ ```jsx
17
+ import { Collapse } from 'startupjs-ui'
18
+ ```
19
+
20
+ ## Simple example
21
+
22
+ ```jsx example noscroll
23
+ const [open, setOpen] = useState(false)
24
+ return (
25
+ <Collapse title='Simple collapse' open={open} onChange={() => setOpen(!open)}>
26
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
27
+ </Collapse>
28
+ )
29
+ ```
30
+
31
+ ## Pure collapse
32
+
33
+ Pure collapse does not contain any styles (background, paddings, etc.). It is useful when `Collapse` is used as a children of other components like `Card`. To make it pure, pass the string `pure` to the `variant` property.
34
+
35
+ ```jsx example noscroll
36
+ const [open, setOpen] = useState(false)
37
+ return (
38
+ <Collapse
39
+ variant='pure'
40
+ open={open}
41
+ onChange={() => setOpen(!open)}
42
+ title='Pure collapse'
43
+ >
44
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
45
+ </Collapse>
46
+ )
47
+ ```
48
+
49
+ ## Advanced usage
50
+
51
+ Collapse consists of two parts - `Header` and `Content`. These parts can be used to add custom markup, the `Header` is used instead of title and the `Content` is used instad of children. They can be used separately. Each part take value of `variant` prop from parent `Collapse` which can be overridden by passing `variant` to itself.
52
+
53
+ ```jsx example noscroll
54
+ const [open, setOpen] = useState(false)
55
+ return (
56
+ <Collapse open={open} onChange={() => setOpen(!open)}>
57
+ <Collapse.Header iconPosition='right'>Advanced collapse</Collapse.Header>
58
+ <Collapse.Content>
59
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
60
+ </Collapse.Content>
61
+ </Collapse>
62
+ )
63
+ ```
64
+
65
+ ## Icon configuration
66
+
67
+ You can provide a custom icon by specifying the `icon` option. It must be an arrow-like icon pointing right.
68
+
69
+ ```jsx example
70
+ const [open, setOpen] = useState(false)
71
+ return (
72
+ <Collapse open={open} onChange={() => setOpen(!open)}>
73
+ <Collapse.Header icon={faAngleRight}>
74
+ Collapse with custom icon
75
+ </Collapse.Header>
76
+ <Collapse.Content>
77
+ Collapse content
78
+ </Collapse.Content>
79
+ </Collapse>
80
+ )
81
+ ```
82
+
83
+ ### No Icon
84
+
85
+ You can remove an icon completely by specifying `icon={false}`
86
+
87
+ ## Accordion
88
+
89
+ Using the `Collapse` component, you can create behavior like an accordion.
90
+
91
+ ```jsx example noscroll
92
+ const [expand, setExpand] = useState()
93
+ const handleChange = (panel) => (shouldExpand) => {
94
+ setExpand(shouldExpand ? panel : null)
95
+ }
96
+ const collapseStyle = {
97
+ borderTopWidth: 1,
98
+ borderStyle: 'solid',
99
+ borderColor: '#ddd'
100
+ }
101
+ return (
102
+ <Div>
103
+ <Collapse
104
+ open={expand === 'open1'}
105
+ title='Accordion Header - 1'
106
+ onChange={handleChange('open1')}
107
+ >
108
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.
109
+ </Collapse>
110
+ <Collapse
111
+ style={collapseStyle}
112
+ open={expand === 'open2'}
113
+ title='Accordion Header - 2'
114
+ onChange={handleChange('open2')}
115
+ >
116
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.
117
+ </Collapse>
118
+ <Collapse
119
+ style={collapseStyle}
120
+ open={expand === 'open3'}
121
+ title='Accordion Header - 3'
122
+ onChange={handleChange('open3')}
123
+ >
124
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.
125
+ </Collapse>
126
+ </Div>
127
+ )
128
+ ```
129
+
130
+ ## Sandbox
131
+
132
+ ### Collapse
133
+
134
+ <Sandbox
135
+ Component={Collapse}
136
+ props={{ $open: $.session.Props.Collapse.open }}
137
+ $props={$.session.Props.Collapse}
138
+ propsJsonSchema={CollapsePropsJsonSchema}
139
+ {...{ _comment: "HACK to make alive sandbox when user click on 'open' checkbox" }}
140
+ />
141
+
142
+ ### Collapse.Header
143
+
144
+ <Sandbox Component={Collapse.Header} propsJsonSchema={CollapseHeaderPropsJsonSchema} />
145
+
146
+ ### Collapse.Content
147
+
148
+ <Sandbox Component={Collapse.Content} propsJsonSchema={CollapseContentPropsJsonSchema} />
@@ -0,0 +1,5 @@
1
+ _gutter = $UI.gutters.m
2
+
3
+ .root
4
+ &.full
5
+ radius()
package/index.d.ts ADDED
@@ -0,0 +1,45 @@
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 { type CollapseHeaderProps } from './CollapseHeader';
8
+ import './index.cssx.styl';
9
+ export declare const _PropsJsonSchema: {};
10
+ export interface CollapseProps extends Omit<DivProps, 'style' | 'children' | 'variant' | 'align'> {
11
+ /** Custom styles applied to the root view */
12
+ style?: StyleProp<ViewStyle>;
13
+ /** Content rendered inside Collapse or via Collapse.Content */
14
+ children?: ReactNode;
15
+ /** Header title when no custom Collapse.Header is provided */
16
+ title?: ReactNode;
17
+ /** Controlled open state @default false */
18
+ open?: boolean;
19
+ /** Scoped model for open state */
20
+ $open?: any;
21
+ /** Visual appearance variant @default 'full' */
22
+ variant?: 'full' | 'pure';
23
+ /** Icon displayed in header; true uses default caret @default true */
24
+ icon?: CollapseHeaderProps['icon'];
25
+ /** Called when open state changes */
26
+ onChange?: (open: boolean) => void;
27
+ /** Custom styles applied to the collapsible container */
28
+ collapsibleContainerStyle?: StyleProp<ViewStyle>;
29
+ /** Alignment of collapsed content */
30
+ align?: 'top' | 'center' | 'bottom';
31
+ /** Height when collapsed */
32
+ collapsedHeight?: number;
33
+ /** Enable pointer events while collapsed */
34
+ enablePointerEvents?: boolean;
35
+ /** Animation duration in ms */
36
+ duration?: number;
37
+ /** Easing function or preset name */
38
+ easing?: ((value: number) => number) | string;
39
+ /** Render children even when collapsed */
40
+ renderChildrenCollapsed?: boolean;
41
+ /** Callback fired after collapse animation ends */
42
+ onAnimationEnd?: () => void;
43
+ }
44
+ declare const ObservedCollapse: any;
45
+ export default ObservedCollapse;
package/index.tsx ADDED
@@ -0,0 +1,130 @@
1
+ import React, { type ReactNode } from 'react'
2
+ import { type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer, useBind } from 'startupjs'
4
+ import { themed } from '@startupjs-ui/core'
5
+ import Div, { type DivProps } from '@startupjs-ui/div'
6
+ import CollapseHeader, { type CollapseHeaderProps } from './CollapseHeader'
7
+ import CollapseContent from './CollapseContent'
8
+ import './index.cssx.styl'
9
+
10
+ export const _PropsJsonSchema = {/* CollapseProps */}
11
+
12
+ export interface CollapseProps extends Omit<DivProps, 'style' | 'children' | 'variant' | 'align'> {
13
+ /** Custom styles applied to the root view */
14
+ style?: StyleProp<ViewStyle>
15
+ /** Content rendered inside Collapse or via Collapse.Content */
16
+ children?: ReactNode
17
+ /** Header title when no custom Collapse.Header is provided */
18
+ title?: ReactNode
19
+ /** Controlled open state @default false */
20
+ open?: boolean
21
+ /** Scoped model for open state */
22
+ $open?: any
23
+ /** Visual appearance variant @default 'full' */
24
+ variant?: 'full' | 'pure'
25
+ /** Icon displayed in header; true uses default caret @default true */
26
+ icon?: CollapseHeaderProps['icon']
27
+ /** Called when open state changes */
28
+ onChange?: (open: boolean) => void
29
+ /** Custom styles applied to the collapsible container */
30
+ collapsibleContainerStyle?: StyleProp<ViewStyle>
31
+ /** Alignment of collapsed content */
32
+ align?: 'top' | 'center' | 'bottom'
33
+ /** Height when collapsed */
34
+ collapsedHeight?: number
35
+ /** Enable pointer events while collapsed */
36
+ enablePointerEvents?: boolean
37
+ /** Animation duration in ms */
38
+ duration?: number
39
+ /** Easing function or preset name */
40
+ easing?: ((value: number) => number) | string
41
+ /** Render children even when collapsed */
42
+ renderChildrenCollapsed?: boolean
43
+ /** Callback fired after collapse animation ends */
44
+ onAnimationEnd?: () => void
45
+ }
46
+
47
+ function Collapse ({
48
+ style,
49
+ children,
50
+ title,
51
+ open = false,
52
+ $open,
53
+ variant = 'full',
54
+ icon = true,
55
+ onChange,
56
+ collapsibleContainerStyle,
57
+ align,
58
+ collapsedHeight,
59
+ enablePointerEvents,
60
+ duration,
61
+ easing,
62
+ renderChildrenCollapsed,
63
+ onAnimationEnd,
64
+ ...props
65
+ }: CollapseProps): ReactNode {
66
+ ;({ open, onChange } = useBind({ $open, open, onChange }) as any)
67
+
68
+ let header: ReactNode | undefined
69
+ let content: ReactNode | undefined
70
+ const contentChildren: ReactNode[] = []
71
+
72
+ React.Children.forEach(children as any, (child: any) => {
73
+ if (!child) return
74
+
75
+ switch (child.type) {
76
+ case CollapseHeader:
77
+ if (header) throw Error('[ui -> Collapse] You must specify a single <Collapse.Header>')
78
+ header = child
79
+ break
80
+ case CollapseContent:
81
+ if (content) throw Error('[ui -> Collapse] You must specify a single <Collapse.Content>')
82
+ content = child
83
+ break
84
+ default:
85
+ contentChildren.push(child as ReactNode)
86
+ }
87
+ })
88
+
89
+ if (content && contentChildren.length > 0) {
90
+ throw Error('[ui -> Collapse] React elements found directly within <Collapse>. ' +
91
+ 'If <Collapse.Content> is specified, you have to put all your content inside it')
92
+ }
93
+
94
+ const contentProps = {
95
+ open,
96
+ variant,
97
+ style: collapsibleContainerStyle,
98
+ align,
99
+ collapsedHeight,
100
+ enablePointerEvents,
101
+ duration,
102
+ easing,
103
+ renderChildrenCollapsed,
104
+ onAnimationEnd
105
+ }
106
+ content = content
107
+ ? React.cloneElement(content as any, { ...contentProps, ...(content as any).props })
108
+ : React.createElement(CollapseContent, contentProps, contentChildren)
109
+
110
+ const headerProps = { open, variant, icon, onPress }
111
+ header = header
112
+ ? React.cloneElement(header as any, { ...headerProps, ...(header as any).props })
113
+ : React.createElement(CollapseHeader, headerProps, title ?? '')
114
+
115
+ function onPress () {
116
+ onChange?.(!open)
117
+ }
118
+
119
+ return pug`
120
+ Div.root(style=style ...props)
121
+ = header
122
+ = content
123
+ `
124
+ }
125
+
126
+ const ObservedCollapse: any = observer(themed('Collapse', Collapse))
127
+ ObservedCollapse.Header = CollapseHeader
128
+ ObservedCollapse.Content = CollapseContent
129
+
130
+ export default ObservedCollapse
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@startupjs-ui/collapse",
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/span": "^0.1.3",
15
+ "react-native-collapsible": "^1.6.2"
16
+ },
17
+ "peerDependencies": {
18
+ "react": "*",
19
+ "react-native": "*",
20
+ "startupjs": "*"
21
+ },
22
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
23
+ }