cpk-ui 0.5.0 → 0.5.1-rc.2

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.
@@ -16,7 +16,12 @@ export type AccordionBaseProps<T = string, K = string> = {
16
16
  styles?: Styles;
17
17
  size?: AccordionSizeType;
18
18
  shouldAnimate?: boolean;
19
+ /** @deprecated Use expandAllOnStart instead */
19
20
  collapseOnStart?: boolean;
21
+ /** If true, all items start expanded. Defaults to false (all items collapsed) */
22
+ expandAllOnStart?: boolean;
23
+ /** Array of indexes that should be expanded on start. Overrides expandAllOnStart */
24
+ defaultExpandedIndexes?: number[];
20
25
  animDuration?: number;
21
26
  activeOpacity?: number;
22
27
  toggleElementPosition?: 'left' | 'right';
@@ -26,7 +31,7 @@ export type AccordionBaseProps<T = string, K = string> = {
26
31
  onPressItem?: (title: T | string, body: K | string) => void;
27
32
  };
28
33
  export type AccordionProps<T = string, K = string> = AccordionBaseProps<T, K>;
29
- declare function Accordion<T, K>({ style, toggleElementPosition, size, data, ...rest }: AccordionProps<T, K>): ReactElement;
34
+ declare function Accordion<T, K>({ style, toggleElementPosition, size, data, collapseOnStart, expandAllOnStart, defaultExpandedIndexes, ...rest }: AccordionProps<T, K>): ReactElement;
30
35
  declare const _default: typeof Accordion;
31
36
  export default _default;
32
37
  export { Accordion };
@@ -1 +1 @@
1
- {"version":3,"file":"Accordion.d.ts","sourceRoot":"","sources":["../../../../src/components/uis/Accordion/Accordion.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAU,KAAK,YAAY,EAAC,MAAM,OAAO,CAAC;AACxD,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAIlE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AAO3D,KAAK,MAAM,GAAG;IACZ,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,IAAI;IACvD,IAAI,EAAE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,YAAY,CAAC;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,YAAY,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,IAAI,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE9E,iBAAS,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,EACvB,KAAK,EACL,qBAA+B,EAC/B,IAAe,EACf,IAAI,EACJ,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAqBrC;wBAGuC,OAAO,SAAS;AAAxD,wBAAyD;AACzD,OAAO,EAAC,SAAS,EAAC,CAAC"}
1
+ {"version":3,"file":"Accordion.d.ts","sourceRoot":"","sources":["../../../../src/components/uis/Accordion/Accordion.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAU,KAAK,YAAY,EAAC,MAAM,OAAO,CAAC;AACxD,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAIlE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AAO3D,KAAK,MAAM,GAAG;IACZ,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,IAAI;IACvD,IAAI,EAAE,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oFAAoF;IACpF,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,YAAY,CAAC;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,YAAY,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,MAAM,IAAI,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE9E,iBAAS,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,EACvB,KAAK,EACL,qBAA+B,EAC/B,IAAe,EACf,IAAI,EACJ,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAiDrC;wBAGuC,OAAO,SAAS;AAAxD,wBAAyD;AACzD,OAAO,EAAC,SAAS,EAAC,CAAC"}
@@ -6,10 +6,39 @@ import { AccordionItem } from './AccordionItem';
6
6
  const Container = styled(View) `
7
7
  flex-direction: column;
8
8
  `;
9
- function Accordion({ style, toggleElementPosition = 'right', size = 'medium', data, ...rest }) {
9
+ function Accordion({ style, toggleElementPosition = 'right', size = 'medium', data, collapseOnStart, expandAllOnStart, defaultExpandedIndexes, ...rest }) {
10
10
  // Memoize accordion items rendering
11
- const accordionItems = useMemo(() => data.map((datum, titleKey) => (_jsx(AccordionItem, { data: datum, testID: `${titleKey}`, toggleElementPosition: toggleElementPosition, size: size, ...rest }, titleKey))), [data, toggleElementPosition, size, rest]);
12
- return (_jsx(Container, { style: style, children: accordionItems }));
11
+ const accordionItems = useMemo(() => data.map((datum, titleKey) => {
12
+ // Determine if this item should be collapsed on start
13
+ let shouldCollapse;
14
+ if (defaultExpandedIndexes !== undefined &&
15
+ defaultExpandedIndexes.length > 0) {
16
+ // If defaultExpandedIndexes is provided, use it
17
+ shouldCollapse = !defaultExpandedIndexes.includes(titleKey);
18
+ }
19
+ else if (expandAllOnStart !== undefined) {
20
+ // If expandAllOnStart is provided, use it
21
+ shouldCollapse = !expandAllOnStart;
22
+ }
23
+ else if (collapseOnStart !== undefined) {
24
+ // Fallback to deprecated collapseOnStart for backward compatibility
25
+ shouldCollapse = collapseOnStart;
26
+ }
27
+ else {
28
+ // Default: all items collapsed
29
+ shouldCollapse = true;
30
+ }
31
+ return (_jsx(AccordionItem, { data: datum, testID: `${titleKey}`, toggleElementPosition: toggleElementPosition, size: size, collapseOnStart: shouldCollapse, ...rest }, titleKey));
32
+ }), [
33
+ data,
34
+ toggleElementPosition,
35
+ size,
36
+ collapseOnStart,
37
+ expandAllOnStart,
38
+ defaultExpandedIndexes,
39
+ rest,
40
+ ]);
41
+ return _jsx(Container, { style: style, children: accordionItems });
13
42
  }
14
43
  // Export memoized component for better performance
15
44
  export default React.memo(Accordion);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cpk-ui",
3
- "version": "0.5.0",
3
+ "version": "0.5.1-rc.2",
4
4
  "main": "index",
5
5
  "react-native": "index",
6
6
  "module": "index",
@@ -10,7 +10,7 @@ An expandable/collapsible accordion component for organizing content hierarchica
10
10
  ## Features
11
11
  - **Smooth Animations**: Configurable animation duration for expand/collapse
12
12
  - **Flexible Sizing**: Preset sizes and custom numeric values
13
- - **Collapse on Start**: Option to start with all items collapsed
13
+ - **Initial Expansion Control**: Control which items start expanded
14
14
  - **Item Press Handling**: Callback for item interactions
15
15
  - **Customizable Content**: Flexible data structure for titles and items
16
16
  - **Animation Control**: Can disable animations if needed
@@ -23,29 +23,32 @@ An expandable/collapsible accordion component for organizing content hierarchica
23
23
 
24
24
  ## Usage
25
25
  \`\`\`tsx
26
- <Accordion
27
- data={[
28
- {
29
- title: 'Section 1',
30
- items: ['Item 1', 'Item 2', 'Item 3'],
31
- },
32
- {
33
- title: 'Section 2',
34
- items: ['Item A', 'Item B', 'Item C'],
35
- },
36
- ]}
37
- size="medium"
38
- collapseOnStart={true}
39
- animDuration={200}
40
- shouldAnimate={true}
41
- onPressItem={(item) => console.log('Pressed:', item)}
42
- />
26
+ // Default: All items collapsed
27
+ <Accordion data={data} />
28
+
29
+ // First item expanded
30
+ <Accordion data={data} defaultExpandedIndexes={[0]} />
31
+
32
+ // Multiple items expanded (first and third)
33
+ <Accordion data={data} defaultExpandedIndexes={[0, 2]} />
34
+
35
+ // All items expanded
36
+ <Accordion data={data} expandAllOnStart={true} />
43
37
  \`\`\`
44
38
 
39
+ ## Initial Expansion Props
40
+ - **defaultExpandedIndexes**: Array of indexes to expand on start (e.g., \`[0, 2]\`)
41
+ - **expandAllOnStart**: Boolean to expand all items on start
42
+ - **collapseOnStart**: **@deprecated** Use \`expandAllOnStart={false}\` instead
43
+
44
+ **Priority order:** \`defaultExpandedIndexes\` > \`expandAllOnStart\` > \`collapseOnStart\` (deprecated) > default (all collapsed)
45
+
45
46
  ## Props
46
47
  - **size**: Accordion size ('small', 'medium', 'large', or number)
47
48
  - **data**: Array of accordion sections with title and items
48
- - **collapseOnStart**: Whether sections start collapsed
49
+ - **defaultExpandedIndexes**: Array of indexes to expand on start
50
+ - **expandAllOnStart**: Whether all sections start expanded
51
+ - **collapseOnStart**: **@deprecated** Whether sections start collapsed (use expandAllOnStart instead)
49
52
  - **animDuration**: Animation duration in milliseconds
50
53
  - **shouldAnimate**: Enable/disable animations
51
54
  - **onPressItem**: Callback when an item is pressed
@@ -76,26 +79,82 @@ export default meta;
76
79
 
77
80
  type Story = StoryObj<typeof meta>;
78
81
 
82
+ const defaultData = [
83
+ {
84
+ title: 'Item 1',
85
+ items: ['User', 'Mail', 'Text'],
86
+ },
87
+ {
88
+ title: 'Item 2',
89
+ items: ['User', 'Mail', 'Text'],
90
+ },
91
+ {
92
+ title: 'Item 3',
93
+ items: ['User', 'Mail', 'Text'],
94
+ },
95
+ ];
96
+
79
97
  export const Basic: Story = {
80
98
  args: {
81
99
  size: 'medium',
82
100
  animDuration: 200,
83
101
  collapseOnStart: true,
84
102
  onPressItem: () => {},
85
- data: [
86
- {
87
- title: 'Item 1',
88
- items: ['User', 'Mail', 'Text'],
89
- },
90
- {
91
- title: 'Item 2',
92
- items: ['User', 'Mail', 'Text'],
93
- },
94
- {
95
- title: 'Item 3',
96
- items: ['User', 'Mail', 'Text'],
97
- },
98
- ],
103
+ data: defaultData,
104
+ shouldAnimate: true,
105
+ },
106
+ argTypes: {
107
+ // @ts-expect-error - theme is for storybook control
108
+ theme: {
109
+ control: 'select',
110
+ options: ['light', 'dark'],
111
+ },
112
+ },
113
+ };
114
+
115
+ export const FirstItemExpanded: Story = {
116
+ args: {
117
+ size: 'medium',
118
+ animDuration: 200,
119
+ defaultExpandedIndexes: [0],
120
+ onPressItem: () => {},
121
+ data: defaultData,
122
+ shouldAnimate: true,
123
+ },
124
+ argTypes: {
125
+ // @ts-expect-error - theme is for storybook control
126
+ theme: {
127
+ control: 'select',
128
+ options: ['light', 'dark'],
129
+ },
130
+ },
131
+ };
132
+
133
+ export const MultipleItemsExpanded: Story = {
134
+ args: {
135
+ size: 'medium',
136
+ animDuration: 200,
137
+ defaultExpandedIndexes: [0, 2],
138
+ onPressItem: () => {},
139
+ data: defaultData,
140
+ shouldAnimate: true,
141
+ },
142
+ argTypes: {
143
+ // @ts-expect-error - theme is for storybook control
144
+ theme: {
145
+ control: 'select',
146
+ options: ['light', 'dark'],
147
+ },
148
+ },
149
+ };
150
+
151
+ export const AllItemsExpanded: Story = {
152
+ args: {
153
+ size: 'medium',
154
+ animDuration: 200,
155
+ expandAllOnStart: true,
156
+ onPressItem: () => {},
157
+ data: defaultData,
99
158
  shouldAnimate: true,
100
159
  },
101
160
  argTypes: {
@@ -41,7 +41,7 @@ describe('[Accordion] render test', () => {
41
41
  expect(json).toBeTruthy();
42
42
  });
43
43
 
44
- it('should render collapsed when collapseOnStart props is true', () => {
44
+ it('should render collapsed when collapseOnStart props is true (deprecated)', () => {
45
45
  props = createTestProps({
46
46
  collapseOnStart: true,
47
47
  data: data,
@@ -57,6 +57,88 @@ describe('[Accordion] render test', () => {
57
57
  expect(json).toBeTruthy();
58
58
  });
59
59
 
60
+ it('should render all expanded when expandAllOnStart is true', () => {
61
+ props = createTestProps({
62
+ expandAllOnStart: true,
63
+ data: data,
64
+ renderTitle: (title) => <Text>{title}</Text>,
65
+ renderItem: (item) => <Text>{item}</Text>,
66
+ });
67
+
68
+ component = createComponent(<Accordion {...props} />);
69
+ testingLib = render(component);
70
+
71
+ const json = testingLib.toJSON();
72
+
73
+ expect(json).toBeTruthy();
74
+ });
75
+
76
+ it('should render first item expanded when defaultExpandedIndexes is [0]', () => {
77
+ props = createTestProps({
78
+ defaultExpandedIndexes: [0],
79
+ data: data,
80
+ renderTitle: (title) => <Text>{title}</Text>,
81
+ renderItem: (item) => <Text>{item}</Text>,
82
+ });
83
+
84
+ component = createComponent(<Accordion {...props} />);
85
+ testingLib = render(component);
86
+
87
+ const json = testingLib.toJSON();
88
+
89
+ expect(json).toBeTruthy();
90
+ });
91
+
92
+ it('should render multiple items expanded when defaultExpandedIndexes is [0, 2]', () => {
93
+ props = createTestProps({
94
+ defaultExpandedIndexes: [0, 2],
95
+ data: data,
96
+ renderTitle: (title) => <Text>{title}</Text>,
97
+ renderItem: (item) => <Text>{item}</Text>,
98
+ });
99
+
100
+ component = createComponent(<Accordion {...props} />);
101
+ testingLib = render(component);
102
+
103
+ const json = testingLib.toJSON();
104
+
105
+ expect(json).toBeTruthy();
106
+ });
107
+
108
+ it('should prioritize defaultExpandedIndexes over expandAllOnStart', () => {
109
+ props = createTestProps({
110
+ defaultExpandedIndexes: [0],
111
+ expandAllOnStart: true,
112
+ data: data,
113
+ renderTitle: (title) => <Text>{title}</Text>,
114
+ renderItem: (item) => <Text>{item}</Text>,
115
+ });
116
+
117
+ component = createComponent(<Accordion {...props} />);
118
+ testingLib = render(component);
119
+
120
+ const json = testingLib.toJSON();
121
+
122
+ expect(json).toBeTruthy();
123
+ });
124
+
125
+ it('should prioritize expandAllOnStart over collapseOnStart (deprecated)', () => {
126
+ props = createTestProps({
127
+ expandAllOnStart: false,
128
+ collapseOnStart: false,
129
+ data: data,
130
+ renderTitle: (title) => <Text>{title}</Text>,
131
+ renderItem: (item) => <Text>{item}</Text>,
132
+ });
133
+
134
+ component = createComponent(<Accordion {...props} />);
135
+ testingLib = render(component);
136
+
137
+ const json = testingLib.toJSON();
138
+
139
+ expect(json).toBeTruthy();
140
+ });
141
+
60
142
  it('should operate animation when shouldAnimate props is true', () => {
61
143
  props = createTestProps({
62
144
  shouldAnimate: true,
@@ -27,7 +27,12 @@ export type AccordionBaseProps<T = string, K = string> = {
27
27
  styles?: Styles;
28
28
  size?: AccordionSizeType;
29
29
  shouldAnimate?: boolean;
30
+ /** @deprecated Use expandAllOnStart instead */
30
31
  collapseOnStart?: boolean;
32
+ /** If true, all items start expanded. Defaults to false (all items collapsed) */
33
+ expandAllOnStart?: boolean;
34
+ /** Array of indexes that should be expanded on start. Overrides expandAllOnStart */
35
+ defaultExpandedIndexes?: number[];
31
36
  animDuration?: number;
32
37
  activeOpacity?: number;
33
38
  toggleElementPosition?: 'left' | 'right';
@@ -44,28 +49,59 @@ function Accordion<T, K>({
44
49
  toggleElementPosition = 'right',
45
50
  size = 'medium',
46
51
  data,
52
+ collapseOnStart,
53
+ expandAllOnStart,
54
+ defaultExpandedIndexes,
47
55
  ...rest
48
56
  }: AccordionProps<T, K>): ReactElement {
49
57
  // Memoize accordion items rendering
50
- const accordionItems = useMemo(() =>
51
- data.map((datum, titleKey) => (
52
- <AccordionItem
53
- data={datum}
54
- key={titleKey}
55
- testID={`${titleKey}`}
56
- toggleElementPosition={toggleElementPosition}
57
- size={size}
58
- {...rest}
59
- />
60
- )),
61
- [data, toggleElementPosition, size, rest]
62
- );
58
+ const accordionItems = useMemo(
59
+ () =>
60
+ data.map((datum, titleKey) => {
61
+ // Determine if this item should be collapsed on start
62
+ let shouldCollapse: boolean;
63
+
64
+ if (
65
+ defaultExpandedIndexes !== undefined &&
66
+ defaultExpandedIndexes.length > 0
67
+ ) {
68
+ // If defaultExpandedIndexes is provided, use it
69
+ shouldCollapse = !defaultExpandedIndexes.includes(titleKey);
70
+ } else if (expandAllOnStart !== undefined) {
71
+ // If expandAllOnStart is provided, use it
72
+ shouldCollapse = !expandAllOnStart;
73
+ } else if (collapseOnStart !== undefined) {
74
+ // Fallback to deprecated collapseOnStart for backward compatibility
75
+ shouldCollapse = collapseOnStart;
76
+ } else {
77
+ // Default: all items collapsed
78
+ shouldCollapse = true;
79
+ }
63
80
 
64
- return (
65
- <Container style={style}>
66
- {accordionItems}
67
- </Container>
81
+ return (
82
+ <AccordionItem
83
+ data={datum}
84
+ key={titleKey}
85
+ testID={`${titleKey}`}
86
+ toggleElementPosition={toggleElementPosition}
87
+ size={size}
88
+ collapseOnStart={shouldCollapse}
89
+ {...rest}
90
+ />
91
+ );
92
+ }),
93
+ [
94
+ data,
95
+ toggleElementPosition,
96
+ size,
97
+ collapseOnStart,
98
+ expandAllOnStart,
99
+ defaultExpandedIndexes,
100
+ rest,
101
+ ],
68
102
  );
103
+
104
+ return <Container style={style}>{accordionItems}</Container>;
69
105
  }
70
106
 
71
107
  // Export memoized component for better performance