@duro-app/ui 0.39.0 → 0.40.0

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.
Files changed (74) hide show
  1. package/dist/components/ActionBar/styles.css.d.ts +2 -2
  2. package/dist/components/ActionBar/styles.css.d.ts.map +1 -1
  3. package/dist/components/Checkbox/styles.css.d.ts +2 -2
  4. package/dist/components/Checkbox/styles.css.d.ts.map +1 -1
  5. package/dist/components/ColorMode/styles.css.d.ts +2 -2
  6. package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
  7. package/dist/components/Combobox/styles.css.d.ts +2 -2
  8. package/dist/components/Combobox/styles.css.d.ts.map +1 -1
  9. package/dist/components/Dialog/styles.css.d.ts +5 -5
  10. package/dist/components/Dialog/styles.css.d.ts.map +1 -1
  11. package/dist/components/Drawer/styles.css.d.ts +13 -13
  12. package/dist/components/Drawer/styles.css.d.ts.map +1 -1
  13. package/dist/components/Input/styles.css.d.ts +2 -2
  14. package/dist/components/Input/styles.css.d.ts.map +1 -1
  15. package/dist/components/InputGroup/styles.css.d.ts +4 -4
  16. package/dist/components/InputGroup/styles.css.d.ts.map +1 -1
  17. package/dist/components/LinkButton/styles.css.d.ts +2 -2
  18. package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
  19. package/dist/components/Menu/styles.css.d.ts +2 -2
  20. package/dist/components/Menu/styles.css.d.ts.map +1 -1
  21. package/dist/components/RadioGroup/styles.css.d.ts +2 -2
  22. package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
  23. package/dist/components/ScrollArea/styles.css.d.ts +3 -3
  24. package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
  25. package/dist/components/Select/styles.css.d.ts +2 -2
  26. package/dist/components/Select/styles.css.d.ts.map +1 -1
  27. package/dist/components/SideNav/styles.css.d.ts +6 -6
  28. package/dist/components/SideNav/styles.css.d.ts.map +1 -1
  29. package/dist/components/Switch/styles.css.d.ts +4 -4
  30. package/dist/components/Switch/styles.css.d.ts.map +1 -1
  31. package/dist/components/Table/styles.css.d.ts +2 -2
  32. package/dist/components/Table/styles.css.d.ts.map +1 -1
  33. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  34. package/dist/components/Tabs/TabsContext.d.ts +2 -0
  35. package/dist/components/Tabs/TabsContext.d.ts.map +1 -1
  36. package/dist/components/Tabs/styles.css.d.ts +33 -2
  37. package/dist/components/Tabs/styles.css.d.ts.map +1 -1
  38. package/dist/components/Tabs/useTabsRoot.d.ts.map +1 -1
  39. package/dist/components/TagGroup/styles.css.d.ts +2 -2
  40. package/dist/components/TagGroup/styles.css.d.ts.map +1 -1
  41. package/dist/components/Textarea/styles.css.d.ts +2 -2
  42. package/dist/components/Textarea/styles.css.d.ts.map +1 -1
  43. package/dist/components/Toast/styles.css.d.ts +1 -1
  44. package/dist/components/Toast/styles.css.d.ts.map +1 -1
  45. package/dist/components/Toggle/styles.css.d.ts +2 -2
  46. package/dist/components/Toggle/styles.css.d.ts.map +1 -1
  47. package/dist/index.css +1 -1
  48. package/dist/index.js +2143 -2066
  49. package/dist/index.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/components/ActionBar/styles.css.ts +3 -2
  52. package/src/components/Checkbox/styles.css.ts +3 -2
  53. package/src/components/ColorMode/styles.css.ts +3 -2
  54. package/src/components/Combobox/styles.css.ts +3 -2
  55. package/src/components/Dialog/styles.css.ts +6 -5
  56. package/src/components/Drawer/styles.css.ts +14 -13
  57. package/src/components/Input/styles.css.ts +3 -2
  58. package/src/components/InputGroup/styles.css.ts +5 -4
  59. package/src/components/LinkButton/styles.css.ts +3 -2
  60. package/src/components/Menu/styles.css.ts +3 -2
  61. package/src/components/RadioGroup/styles.css.ts +3 -2
  62. package/src/components/ScrollArea/styles.css.ts +4 -3
  63. package/src/components/Select/styles.css.ts +3 -2
  64. package/src/components/SideNav/styles.css.ts +7 -6
  65. package/src/components/Switch/styles.css.ts +5 -4
  66. package/src/components/Table/styles.css.ts +3 -2
  67. package/src/components/Tabs/Tabs.tsx +52 -3
  68. package/src/components/Tabs/TabsContext.ts +6 -0
  69. package/src/components/Tabs/styles.css.ts +41 -2
  70. package/src/components/Tabs/useTabsRoot.ts +12 -2
  71. package/src/components/TagGroup/styles.css.ts +3 -2
  72. package/src/components/Textarea/styles.css.ts +3 -2
  73. package/src/components/Toast/styles.css.ts +2 -1
  74. package/src/components/Toggle/styles.css.ts +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,7 +54,7 @@
54
54
  }
55
55
  },
56
56
  "dependencies": {
57
- "@duro-app/tokens": "^0.39.0"
57
+ "@duro-app/tokens": "^0.40.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@babel/preset-typescript": "^7.28.0",
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  overlay: {
@@ -63,8 +64,8 @@ export const styles = css.create({
63
64
  },
64
65
  cursor: 'pointer',
65
66
  transitionProperty: 'color, background-color',
66
- transitionDuration: '150ms',
67
- transitionTimingFunction: 'ease',
67
+ transitionDuration: duration.fast,
68
+ transitionTimingFunction: easing.standard,
68
69
  outlineWidth: {
69
70
  default: 0,
70
71
  ':focus-visible': 2,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  root: {
@@ -28,8 +29,8 @@ export const styles = css.create({
28
29
  justifyContent: 'center',
29
30
  flexShrink: 0,
30
31
  transitionProperty: 'background-color, border-color',
31
- transitionDuration: '150ms',
32
- transitionTimingFunction: 'ease',
32
+ transitionDuration: duration.fast,
33
+ transitionTimingFunction: easing.standard,
33
34
  },
34
35
  boxUnchecked: {
35
36
  backgroundColor: colors.bg,
@@ -1,6 +1,7 @@
1
1
  import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
4
5
 
5
6
  export const styles = css.create({
6
7
  button: {
@@ -22,8 +23,8 @@ export const styles = css.create({
22
23
  color: colors.text,
23
24
  cursor: 'pointer',
24
25
  transitionProperty: 'background-color, border-color, color',
25
- transitionDuration: '150ms',
26
- transitionTimingFunction: 'ease',
26
+ transitionDuration: duration.fast,
27
+ transitionTimingFunction: easing.standard,
27
28
  outlineWidth: {default: 0, ':focus-visible': 2},
28
29
  outlineStyle: 'solid',
29
30
  outlineColor: colors.accent,
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  root: {
@@ -24,7 +25,7 @@ export const styles = css.create({
24
25
  },
25
26
  borderRadius: radii.sm,
26
27
  transitionProperty: 'border-color',
27
- transitionDuration: '150ms',
28
+ transitionDuration: duration.fast,
28
29
  },
29
30
  inputWrapperFocused: {
30
31
  borderColor: colors.accent,
@@ -94,7 +95,7 @@ export const styles = css.create({
94
95
  cursor: 'pointer',
95
96
  backgroundColor: 'transparent',
96
97
  transitionProperty: 'background-color',
97
- transitionDuration: '150ms',
98
+ transitionDuration: duration.fast,
98
99
  },
99
100
  itemSelected: {
100
101
  color: colors.accent,
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  // --- Backdrop ---
@@ -19,7 +20,7 @@ export const styles = css.create({
19
20
  to: {opacity: 1},
20
21
  }),
21
22
  animationDuration: '180ms',
22
- animationTimingFunction: 'ease-out',
23
+ animationTimingFunction: easing.easeOut,
23
24
  animationFillMode: 'both',
24
25
  },
25
26
  backdropClosing: {
@@ -29,7 +30,7 @@ export const styles = css.create({
29
30
  to: {opacity: 0},
30
31
  }),
31
32
  animationDuration: '140ms',
32
- animationTimingFunction: 'ease-in',
33
+ animationTimingFunction: easing.easeIn,
33
34
  animationFillMode: 'both',
34
35
  },
35
36
 
@@ -75,7 +76,7 @@ export const styles = css.create({
75
76
  },
76
77
  }),
77
78
  animationDuration: '180ms',
78
- animationTimingFunction: 'ease-out',
79
+ animationTimingFunction: easing.easeOut,
79
80
  animationFillMode: 'both',
80
81
  },
81
82
  popupClosing: {
@@ -91,7 +92,7 @@ export const styles = css.create({
91
92
  },
92
93
  }),
93
94
  animationDuration: '140ms',
94
- animationTimingFunction: 'ease-in',
95
+ animationTimingFunction: easing.easeIn,
95
96
  animationFillMode: 'both',
96
97
  },
97
98
 
@@ -174,7 +175,7 @@ export const styles = css.create({
174
175
  padding: 0,
175
176
  flexShrink: 0,
176
177
  transitionProperty: 'background-color',
177
- transitionDuration: '150ms',
178
+ transitionDuration: duration.fast,
178
179
  },
179
180
 
180
181
  // --- Inline wrapper (for Trigger / Close children) ---
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  // --- Backdrop ---
@@ -17,8 +18,8 @@ export const styles = css.create({
17
18
  from: {opacity: 0},
18
19
  to: {opacity: 1},
19
20
  }),
20
- animationDuration: '200ms',
21
- animationTimingFunction: 'ease-out',
21
+ animationDuration: duration.base,
22
+ animationTimingFunction: easing.easeOut,
22
23
  animationFillMode: 'both',
23
24
  },
24
25
  backdropClosing: {
@@ -27,7 +28,7 @@ export const styles = css.create({
27
28
  to: {opacity: 0},
28
29
  }),
29
30
  animationDuration: '160ms',
30
- animationTimingFunction: 'ease-in',
31
+ animationTimingFunction: easing.easeIn,
31
32
  animationFillMode: 'both',
32
33
  },
33
34
 
@@ -104,8 +105,8 @@ export const styles = css.create({
104
105
  from: {transform: 'translateX(100%)'},
105
106
  to: {transform: 'translateX(0)'},
106
107
  }),
107
- animationDuration: '200ms',
108
- animationTimingFunction: 'ease-out',
108
+ animationDuration: duration.base,
109
+ animationTimingFunction: easing.easeOut,
109
110
  animationFillMode: 'both',
110
111
  },
111
112
  slideOutRight: {
@@ -114,7 +115,7 @@ export const styles = css.create({
114
115
  to: {transform: 'translateX(100%)'},
115
116
  }),
116
117
  animationDuration: '160ms',
117
- animationTimingFunction: 'ease-in',
118
+ animationTimingFunction: easing.easeIn,
118
119
  animationFillMode: 'both',
119
120
  },
120
121
 
@@ -124,8 +125,8 @@ export const styles = css.create({
124
125
  from: {transform: 'translateX(-100%)'},
125
126
  to: {transform: 'translateX(0)'},
126
127
  }),
127
- animationDuration: '200ms',
128
- animationTimingFunction: 'ease-out',
128
+ animationDuration: duration.base,
129
+ animationTimingFunction: easing.easeOut,
129
130
  animationFillMode: 'both',
130
131
  },
131
132
  slideOutLeft: {
@@ -134,7 +135,7 @@ export const styles = css.create({
134
135
  to: {transform: 'translateX(-100%)'},
135
136
  }),
136
137
  animationDuration: '160ms',
137
- animationTimingFunction: 'ease-in',
138
+ animationTimingFunction: easing.easeIn,
138
139
  animationFillMode: 'both',
139
140
  },
140
141
 
@@ -144,8 +145,8 @@ export const styles = css.create({
144
145
  from: {transform: 'translateY(100%)'},
145
146
  to: {transform: 'translateY(0)'},
146
147
  }),
147
- animationDuration: '200ms',
148
- animationTimingFunction: 'ease-out',
148
+ animationDuration: duration.base,
149
+ animationTimingFunction: easing.easeOut,
149
150
  animationFillMode: 'both',
150
151
  },
151
152
  slideOutBottom: {
@@ -154,7 +155,7 @@ export const styles = css.create({
154
155
  to: {transform: 'translateY(100%)'},
155
156
  }),
156
157
  animationDuration: '160ms',
157
- animationTimingFunction: 'ease-in',
158
+ animationTimingFunction: easing.easeIn,
158
159
  animationFillMode: 'both',
159
160
  },
160
161
 
@@ -232,7 +233,7 @@ export const styles = css.create({
232
233
  padding: 0,
233
234
  flexShrink: 0,
234
235
  transitionProperty: 'background-color',
235
- transitionDuration: '150ms',
236
+ transitionDuration: duration.fast,
236
237
  },
237
238
 
238
239
  // --- Inline wrapper ---
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  base: {
@@ -19,8 +20,8 @@ export const styles = css.create({
19
20
  borderStyle: 'solid',
20
21
  borderRadius: radii.sm,
21
22
  transitionProperty: 'border-color',
22
- transitionDuration: '150ms',
23
- transitionTimingFunction: 'ease',
23
+ transitionDuration: duration.fast,
24
+ transitionTimingFunction: easing.standard,
24
25
  outlineWidth: {
25
26
  default: 0,
26
27
  ':focus-visible': 2,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  wrapper: {
@@ -16,8 +17,8 @@ export const styles = css.create({
16
17
  borderRadius: radii.sm,
17
18
  overflow: 'hidden',
18
19
  transitionProperty: 'border-color',
19
- transitionDuration: '150ms',
20
- transitionTimingFunction: 'ease',
20
+ transitionDuration: duration.fast,
21
+ transitionTimingFunction: easing.standard,
21
22
  },
22
23
  addon: {
23
24
  display: 'inline-flex',
@@ -51,8 +52,8 @@ export const styles = css.create({
51
52
  ':active': colors.bg,
52
53
  },
53
54
  transitionProperty: 'background-color',
54
- transitionDuration: '150ms',
55
- transitionTimingFunction: 'ease',
55
+ transitionDuration: duration.fast,
56
+ transitionTimingFunction: easing.standard,
56
57
  },
57
58
  addonDisabled: {
58
59
  opacity: 0.4,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  base: {
@@ -17,8 +18,8 @@ export const styles = css.create({
17
18
  cursor: 'pointer',
18
19
  textDecoration: 'none',
19
20
  transitionProperty: 'background-color, border-color, color',
20
- transitionDuration: '150ms',
21
- transitionTimingFunction: 'ease',
21
+ transitionDuration: duration.fast,
22
+ transitionTimingFunction: easing.standard,
22
23
  },
23
24
  sizeDefault: {
24
25
  paddingTop: spacing.sm,
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  root: {
@@ -30,7 +31,7 @@ export const styles = css.create({
30
31
  borderRadius: radii.sm,
31
32
  cursor: 'pointer',
32
33
  transitionProperty: 'background-color, border-color',
33
- transitionDuration: '150ms',
34
+ transitionDuration: duration.fast,
34
35
  },
35
36
  backdrop: {
36
37
  position: 'fixed',
@@ -74,7 +75,7 @@ export const styles = css.create({
74
75
  cursor: 'pointer',
75
76
  backgroundColor: 'transparent',
76
77
  transitionProperty: 'background-color',
77
- transitionDuration: '150ms',
78
+ transitionDuration: duration.fast,
78
79
  },
79
80
  itemHighlighted: {
80
81
  backgroundColor: colors.bgCardHover,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  root: {
@@ -36,8 +37,8 @@ export const styles = css.create({
36
37
  justifyContent: 'center',
37
38
  flexShrink: 0,
38
39
  transitionProperty: 'background-color, border-color',
39
- transitionDuration: '150ms',
40
- transitionTimingFunction: 'ease',
40
+ transitionDuration: duration.fast,
41
+ transitionTimingFunction: easing.standard,
41
42
  },
42
43
  circleUnchecked: {
43
44
  backgroundColor: colors.bg,
@@ -1,6 +1,7 @@
1
1
  import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {radii} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
4
5
 
5
6
  export const styles = css.create({
6
7
  root: {
@@ -26,8 +27,8 @@ export const styles = css.create({
26
27
  touchAction: 'none',
27
28
  userSelect: 'none',
28
29
  transitionProperty: 'opacity',
29
- transitionDuration: '200ms',
30
- transitionTimingFunction: 'ease',
30
+ transitionDuration: duration.base,
31
+ transitionTimingFunction: easing.standard,
31
32
  },
32
33
  scrollbarVertical: {
33
34
  top: 0,
@@ -64,7 +65,7 @@ export const styles = css.create({
64
65
  },
65
66
  borderRadius: radii.full,
66
67
  transitionProperty: 'background-color',
67
- transitionDuration: '150ms',
68
+ transitionDuration: duration.fast,
68
69
  },
69
70
  // Dynamic styles — simple identifier params only (StyleX constraint)
70
71
  viewportMaxHeight: (maxHeight: number | string) => ({
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
+ import {duration} from '@duro-app/tokens/tokens/motion.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  root: {
@@ -31,7 +32,7 @@ export const styles = css.create({
31
32
  borderRadius: radii.sm,
32
33
  cursor: 'pointer',
33
34
  transitionProperty: 'border-color',
34
- transitionDuration: '150ms',
35
+ transitionDuration: duration.fast,
35
36
  },
36
37
  value: {
37
38
  color: colors.text,
@@ -94,7 +95,7 @@ export const styles = css.create({
94
95
  cursor: 'pointer',
95
96
  backgroundColor: 'transparent',
96
97
  transitionProperty: 'background-color',
97
- transitionDuration: '150ms',
98
+ transitionDuration: duration.fast,
98
99
  },
99
100
  itemSelected: {
100
101
  color: colors.accent,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  root: {
@@ -34,8 +35,8 @@ export const styles = css.create({
34
35
  borderWidth: 0,
35
36
  cursor: 'pointer',
36
37
  transitionProperty: 'color',
37
- transitionDuration: '150ms',
38
- transitionTimingFunction: 'ease',
38
+ transitionDuration: duration.fast,
39
+ transitionTimingFunction: easing.standard,
39
40
  },
40
41
  groupTriggerActive: {
41
42
  color: colors.text,
@@ -44,8 +45,8 @@ export const styles = css.create({
44
45
  display: 'inline-flex',
45
46
  alignItems: 'center',
46
47
  transitionProperty: 'transform',
47
- transitionDuration: '150ms',
48
- transitionTimingFunction: 'ease',
48
+ transitionDuration: duration.fast,
49
+ transitionTimingFunction: easing.standard,
49
50
  },
50
51
  chevronOpen: {
51
52
  transform: 'rotate(90deg)',
@@ -72,8 +73,8 @@ export const styles = css.create({
72
73
  borderRadius: radii.sm,
73
74
  cursor: 'pointer',
74
75
  transitionProperty: 'color, background-color',
75
- transitionDuration: '150ms',
76
- transitionTimingFunction: 'ease',
76
+ transitionDuration: duration.fast,
77
+ transitionTimingFunction: easing.standard,
77
78
  textAlign: 'left' as const,
78
79
  outlineWidth: {
79
80
  default: 0,
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  export const styles = css.create({
7
8
  root: {
@@ -26,8 +27,8 @@ export const styles = css.create({
26
27
  padding: 0,
27
28
  cursor: 'inherit',
28
29
  transitionProperty: 'background-color',
29
- transitionDuration: '150ms',
30
- transitionTimingFunction: 'ease',
30
+ transitionDuration: duration.fast,
31
+ transitionTimingFunction: easing.standard,
31
32
  outlineWidth: {default: 0, ':focus-visible': 2},
32
33
  outlineStyle: 'solid',
33
34
  outlineColor: colors.accent,
@@ -55,8 +56,8 @@ export const styles = css.create({
55
56
  borderRadius: radii.full,
56
57
  backgroundColor: '#fff',
57
58
  transitionProperty: 'transform',
58
- transitionDuration: '150ms',
59
- transitionTimingFunction: 'ease',
59
+ transitionDuration: duration.fast,
60
+ transitionTimingFunction: easing.standard,
60
61
  },
61
62
  thumbChecked: {
62
63
  transform: 'translateX(16px)',
@@ -2,6 +2,7 @@ import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
5
6
 
6
7
  // Container-query thresholds. Picked for typical 5-7 column admin tables —
7
8
  // see Table.stories.tsx "Responsive" for the visual reasoning. Defined
@@ -129,8 +130,8 @@ export const styles = css.create({
129
130
  ':hover': colors.bgCardHover,
130
131
  },
131
132
  transitionProperty: 'background-color',
132
- transitionDuration: '150ms',
133
- transitionTimingFunction: 'ease',
133
+ transitionDuration: duration.fast,
134
+ transitionTimingFunction: easing.standard,
134
135
  // Stack-mode card chrome
135
136
  padding: {
136
137
  default: 0,
@@ -1,4 +1,4 @@
1
- import {type ReactNode, useRef, useId, useEffect} from 'react'
1
+ import {type ReactNode, useRef, useId, useEffect, useLayoutEffect, useState} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
3
  import {styles} from './styles.css'
4
4
  import {TabsContext, useTabs} from './TabsContext'
@@ -39,8 +39,44 @@ interface ListProps {
39
39
  }
40
40
 
41
41
  function List({children}: ListProps) {
42
- const {orientation, activeValue, onSelect, tabsRef, orderRef} = useTabs()
42
+ const {orientation, activeValue, onSelect, tabsRef, orderRef, setIndicatorActive} = useTabs()
43
43
  const listRef = useRef<HTMLDivElement>(null)
44
+ const [indicator, setIndicator] = useState<{offset: number; size: number} | null>(null)
45
+
46
+ // Measure the active tab and drive the sliding indicator. useLayoutEffect so
47
+ // the first position is set before paint (the indicator element mounts already
48
+ // in place — no slide-in from zero; only later tab changes animate). Observes
49
+ // the active tab (its own size, e.g. a changing "(3)" count) and the list
50
+ // (container resize / reflow). A non-active sibling resizing while you're on
51
+ // another tab is the one case not caught; it self-corrects on the next
52
+ // selection or resize.
53
+ useLayoutEffect(() => {
54
+ const list = listRef.current
55
+ if (!list || !activeValue) {
56
+ setIndicator(null)
57
+ setIndicatorActive(false)
58
+ return
59
+ }
60
+ const el = list.querySelector<HTMLElement>(`[data-tab-value="${CSS.escape(activeValue)}"]`)
61
+ if (!el) {
62
+ setIndicator(null)
63
+ setIndicatorActive(false)
64
+ return
65
+ }
66
+ const measure = () => {
67
+ setIndicator(
68
+ orientation === 'horizontal'
69
+ ? {offset: el.offsetLeft, size: el.offsetWidth}
70
+ : {offset: el.offsetTop, size: el.offsetHeight},
71
+ )
72
+ setIndicatorActive(true)
73
+ }
74
+ measure()
75
+ const ro = new ResizeObserver(measure)
76
+ ro.observe(el)
77
+ ro.observe(list)
78
+ return () => ro.disconnect()
79
+ }, [activeValue, orientation, setIndicatorActive])
44
80
 
45
81
  useEffect(() => {
46
82
  const el = listRef.current
@@ -128,6 +164,18 @@ function List({children}: ListProps) {
128
164
  style={[styles.list, orientation === 'vertical' && styles.listVertical]}
129
165
  >
130
166
  {children}
167
+ {indicator && (
168
+ <html.div
169
+ aria-hidden
170
+ style={[
171
+ styles.indicator,
172
+ orientation === 'horizontal' ? styles.indicatorHorizontal : styles.indicatorVertical,
173
+ orientation === 'horizontal'
174
+ ? styles.indicatorOffsetH(indicator.offset, indicator.size)
175
+ : styles.indicatorOffsetV(indicator.offset, indicator.size),
176
+ ]}
177
+ />
178
+ )}
131
179
  </html.div>
132
180
  )
133
181
  }
@@ -141,7 +189,7 @@ interface TabProps {
141
189
  }
142
190
 
143
191
  function Tab({value, disabled = false, children}: TabProps) {
144
- const {activeValue, onSelect, orientation, registerTab} = useTabs()
192
+ const {activeValue, onSelect, orientation, registerTab, indicatorActive} = useTabs()
145
193
  const isActive = activeValue === value
146
194
  const tabId = useId()
147
195
  const panelId = `${tabId}-panel`
@@ -172,6 +220,7 @@ function Tab({value, disabled = false, children}: TabProps) {
172
220
  orientation === 'vertical' && styles.tabVertical,
173
221
  isActive &&
174
222
  (orientation === 'vertical' ? styles.tabActiveVertical : styles.tabActiveHorizontal),
223
+ isActive && indicatorActive && styles.tabIndicatorActive,
175
224
  disabled && styles.tabDisabled,
176
225
  ]}
177
226
  >
@@ -9,6 +9,12 @@ export interface TabsContextValue {
9
9
  registerTab: (value: string, disabled: boolean) => () => void
10
10
  tabsRef: React.RefObject<Map<string, boolean>>
11
11
  orderRef: React.RefObject<string[]>
12
+ // True once the List has measured the active tab and is driving a sliding
13
+ // indicator. Tabs suppress their own accent border while it's true so the
14
+ // single sliding bar is the only underline; it stays false without JS
15
+ // (SSR / no-hydration), where the per-tab border is the graceful fallback.
16
+ indicatorActive: boolean
17
+ setIndicatorActive: (active: boolean) => void
12
18
  }
13
19
 
14
20
  export const TabsContext = createContext<TabsContextValue | null>(null)