Skip to content

Commit 0973002

Browse files
Merge pull request #58 from infinum/release/5.4.0
5.4.0
2 parents 6c3c86a + a28561d commit 0973002

File tree

8 files changed

+498
-277
lines changed

8 files changed

+498
-277
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
33

44
This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/).
55

6+
## [5.4.0] - 2025-10-07
7+
- Added `FilePickerShell` component
8+
- Added 4 new `Button` types: `glass`, `glassDark`, `dangerGlass`, and `selectedGlass`
9+
- Added `flat` prop to `Button`
10+
- Updated dependencies
11+
612
## [5.3.3] - 2025-09-25
713
- Added `event.stopPropagation` by default on `MenuItem`s. This can be overridden with the new `onClickNative` prop, which receives the native click event.
814
- Fixed `ToggleButton` styles.
@@ -422,6 +428,7 @@ Co-authored with @piqusy
422428
- Initial release
423429

424430
[Unreleased]: https://github.com/infinum/eightshift-ui-components/compare/master...HEAD
431+
[5.4.0]: https://github.com/infinum/eightshift-ui-components/compare/5.3.3...5.4.0
425432
[5.3.3]: https://github.com/infinum/eightshift-ui-components/compare/5.3.2...5.3.3
426433
[5.3.2]: https://github.com/infinum/eightshift-ui-components/compare/5.3.1...5.3.2
427434
[5.3.1]: https://github.com/infinum/eightshift-ui-components/compare/5.3.0...5.3.1

bun.lock

Lines changed: 267 additions & 243 deletions
Large diffs are not rendered by default.

lib/components/button/button.jsx

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ import { icons } from '../../icons';
2727
* @property {TooltipProps} [props.tooltipProps] - Props to pass to the tooltip.
2828
* @property {boolean} [props.pending] - If `true`, the button is in a pending state, which can be used to indicate that an action is being processed.
2929
* @property {string} [props.pendingAriaLabel='Loading'] - ARIA label for the pending state, used for screen readers.
30+
* @property {boolean} [props.flat] - If `true`, the button will be render with a more flat look. (applies only to `default`, `selected`, and `danger` types)
3031
* @property {boolean} [props.hidden] - If `true`, the component is not rendered.
3132
*
3233
* @typedef {'small' | 'default' | 'large'} ButtonSize
33-
* @typedef {'default' | 'selected' | 'selectedGhost' | 'ghost' | 'danger' | 'dangerGhost'} ButtonType
34+
* @typedef {'default' | 'selected' | 'selectedGhost' | 'ghost' | 'danger' | 'dangerGhost' | 'glass' | 'glassDark' | 'dangerGlass' | 'selectedGlass'} ButtonType
3435
*
3536
* @preserve
3637
*/
@@ -56,6 +57,7 @@ export const Button = (props) => {
5657
icon,
5758
size = 'default',
5859
type = 'default',
60+
flat,
5961
pending,
6062
pendingAriaLabel = __('Loading', 'eightshift-ui-components'),
6163
disabled,
@@ -106,25 +108,74 @@ export const Button = (props) => {
106108
large: 'es:icon:size-6 es:rounded-xl',
107109
},
108110
type: {
109-
default: 'es:bg-radial-[at_50%_125%] es:inset-ring es:inset-shadow-xs',
110-
selected: 'es:bg-radial-[at_50%_125%] es:inset-ring es:inset-shadow-xs',
111-
danger: 'es:bg-radial-[at_50%_125%] es:inset-ring es:inset-shadow-xs',
111+
default: ['es:bg-radial-[at_50%_125%]', !flat && 'es:inset-ring es:inset-shadow-xs'],
112+
selected: ['es:bg-radial-[at_50%_125%]', !flat && 'es:inset-ring es:inset-shadow-xs'],
113+
danger: ['es:bg-radial-[at_50%_125%]', !flat && 'es:inset-ring es:inset-shadow-xs'],
112114
ghost:
113-
'es:border-transparent es:text-secondary-700 es:enabled:hover:bg-secondary-100 es:enabled:active:bg-accent-50 es:enabled:pressed:bg-accent-50 es:enabled:active:text-accent-950 es:enabled:pressed:text-accent-950 es:disabled:border-transparent!',
115+
'es:border-transparent es:text-secondary-700 es:hover:bg-secondary-100 es:enabled:active:bg-accent-50 es:enabled:pressed:bg-accent-50 es:enabled:active:text-accent-950 es:enabled:pressed:text-accent-950 es:disabled:border-transparent!',
114116
dangerGhost: [
115117
'es:border-transparent es:text-red-700',
116-
'es:enabled:hover:bg-red-500/5 es:enabled:active:bg-red-500/10 es:enabled:pressed:bg-red-500/10',
118+
'es:hover:bg-red-500/5 es:enabled:active:bg-red-500/10 es:enabled:pressed:bg-red-500/10',
117119
'es:focus-visible:text-red-700',
118120
'es:focus-visible:ring-red-500/30 es:focus-visible:border-red-600 es:focus-visible:inset-ring-red-100',
119121
'es:disabled:border-transparent!',
120122
],
121123
selectedGhost: [
122124
'es:border-transparent es:text-accent-600',
123-
'es:enabled:hover:bg-accent-500/5 es:enabled:active:bg-accent-500/10 es:enabled:pressed:bg-accent-500/10',
125+
'es:hover:bg-accent-500/5 es:enabled:active:bg-accent-500/10 es:enabled:pressed:bg-accent-500/10',
124126
'es:focus-visible:text-accent-700',
125127
'es:focus-visible:ring-accent-500/30 es:focus-visible:border-accent-500 es:focus-visible:inset-ring-accent-100',
126128
'es:disabled:border-transparent!',
127129
],
130+
glass: [
131+
'es:backdrop-blur-md',
132+
'es:backdrop-saturate-120 es:hover:backdrop-saturate-130',
133+
'es:bg-radial-[at_50%_75%] es:from-white/20 es:to-white/15',
134+
'es:hover:from-white/30 es:hover:to-white/25',
135+
'es:focus-visible:from-white/90 es:focus-visible:to-white/85',
136+
'es:border-none es:text-white es:focus-visible:text-black',
137+
'es:inset-shadow-sm es:inset-shadow-white/5 es:hover:inset-shadow-white/10',
138+
'es:shadow-xs es:shadow-black/10',
139+
'es:inset-ring es:inset-ring-white/2',
140+
'es:text-shadow-2xs es:text-shadow-black/30',
141+
],
142+
glassDark: [
143+
'es:backdrop-blur-md',
144+
'es:backdrop-saturate-115 es:hover:backdrop-saturate-125',
145+
'es:bg-radial-[at_50%_75%] es:from-black/40 es:to-black/35',
146+
'es:hover:from-black/50 es:hover:to-black/45',
147+
'es:focus-visible:from-black/95 es:focus-visible:to-black/90',
148+
'es:border-none es:text-white',
149+
'es:inset-shadow-sm es:inset-shadow-white/5 es:hover:inset-shadow-white/10',
150+
'es:shadow-xs es:shadow-black/10',
151+
'es:inset-ring es:inset-ring-black/10',
152+
'es:text-shadow-2xs es:text-shadow-black/30 es:focus-visible:text-shadow-black/0',
153+
],
154+
dangerGlass: [
155+
'es:backdrop-blur-md',
156+
'es:backdrop-saturate-115 es:hover:backdrop-saturate-125',
157+
'es:bg-radial-[at_50%_75%] es:from-red-700/50 es:to-red-700/45',
158+
'es:hover:from-red-700/60 es:hover:to-red-700/55',
159+
'es:enabled:focus-visible:from-red-700/95 es:enabled:focus-visible:to-red-700/90',
160+
'es:border-none es:text-white',
161+
'es:inset-shadow-sm es:inset-shadow-white/5 es:hover:inset-shadow-white/10',
162+
'es:shadow-xs es:shadow-red-700/10',
163+
'es:inset-ring es:inset-ring-red-700/10',
164+
'es:text-shadow-2xs es:text-shadow-black/30',
165+
'es:focus-visible:ring-red-500/30',
166+
],
167+
selectedGlass: [
168+
'es:backdrop-blur-md',
169+
'es:backdrop-saturate-120 es:hover:backdrop-saturate-130',
170+
'es:bg-radial-[at_50%_75%] es:from-accent-400/35 es:to-accent-400/30',
171+
'es:hover:from-accent-400/45 es:hover:to-accent-400/40',
172+
'es:focus-visible:from-accent-600/95 es:focus-visible:to-accent-600/90',
173+
'es:border-none es:text-white',
174+
'es:inset-shadow-sm es:inset-shadow-white/5 es:hover:inset-shadow-white/10',
175+
'es:shadow-xs es:shadow-black/10',
176+
'es:inset-ring es:inset-ring-white/2',
177+
'es:text-shadow-2xs es:text-shadow-black/30',
178+
],
128179
},
129180
},
130181
compoundVariants: [
@@ -136,10 +187,11 @@ export const Button = (props) => {
136187
'es:text-black',
137188
'es:from-white es:to-secondary-50',
138189
'es:border-secondary-300',
139-
'es:inset-ring-secondary-100',
140-
'es:inset-shadow-secondary-100/50',
141-
'es:shadow-sm',
142-
'es:enabled:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm es:hover:inset-shadow-secondary-100 es:hover:to-secondary-100 es:hover:inset-ring-secondary-100',
190+
!flat && 'es:inset-ring-secondary-100',
191+
!flat && 'es:inset-shadow-secondary-100/50 es:hover:inset-shadow-secondary-100',
192+
!flat && 'es:shadow-sm',
193+
!flat && 'es:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm',
194+
flat ? 'es:hover:from-secondary-100 es:hover:to-secondary-100 es:hover:inset-ring-secondary-100' : 'es:hover:to-secondary-100 es:hover:inset-ring-secondary-100',
143195
'es:hover:text-accent-950',
144196
'es:focus-visible:text-accent-950',
145197
],
@@ -149,29 +201,30 @@ export const Button = (props) => {
149201
disabled: false,
150202
class: [
151203
'es:text-white',
152-
'es:from-accent-500 es:to-accent-600',
204+
flat ? 'es:from-accent-500 es:to-accent-600 es:hover:from-accent-600 es:hover:to-accent-700' : 'es:from-accent-500 es:to-accent-600',
153205
'es:border-accent-700',
154-
'es:inset-ring es:inset-ring-accent-600',
155-
'es:inset-shadow-xs es:inset-shadow-accent-400/75',
206+
!flat && 'es:inset-ring es:inset-ring-accent-600',
207+
!flat && 'es:inset-shadow-xs es:inset-shadow-accent-400/75',
156208
'es:focus-visible:border-accent-700',
157209
'es:focus-visible:inset-ring es:focus-visible:inset-ring-accent-600',
158210
'es:focus-visible:inset-shadow-xs es:focus-visible:inset-shadow-accent-400',
159-
'es:shadow es:shadow-accent-600/30 es:enabled:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm',
211+
!flat && 'es:shadow es:shadow-accent-600/30 es:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm',
212+
'es:text-shadow-2xs es:text-shadow-black/20',
160213
],
161214
},
162215
{
163216
type: 'danger',
164217
disabled: false,
165218
class: [
166219
'es:text-red-700',
167-
'es:from-red-50/75 es:to-white',
220+
flat ? 'es:from-red-600/5 es:to-red-600/2 es:hover:from-red-600/10 es:hover:to-red-600/5' : 'es:from-red-50/75 es:to-white',
168221
'es:border-red-700/50',
169-
'es:inset-ring-red-100',
170-
'es:inset-shadow-red-50',
171-
'es:hover:inset-shadow-red-100 es:hover:inset-ring-red-100 es:hover:text-red-800 es:hover:border-red-600',
222+
!flat && 'es:inset-ring-red-100',
223+
!flat && 'es:inset-shadow-red-50',
224+
!flat && 'es:hover:inset-shadow-red-100 es:hover:inset-ring-red-100 es:hover:text-red-800 es:hover:border-red-600',
172225
'es:focus-visible:text-red-900',
173226
'es:focus-visible:ring-red-500/30 es:focus-visible:border-red-600 es:focus-visible:inset-ring-red-100',
174-
'es:shadow es:shadow-red-700/20 es:enabled:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm',
227+
!flat && 'es:shadow es:shadow-red-700/20 es:hover:shadow-md es:enabled:active:shadow-sm es:enabled:pressed:shadow-sm',
175228
],
176229
},
177230
{

lib/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { DraggableContext } from './draggable/draggable-context';
1414
export { Draggable } from './draggable/draggable';
1515
export { DraggableHandle } from './draggable/draggable-handle';
1616
export { Expandable } from './expandable/expandable';
17+
export { FilePickerShell } from './placeholders/file-picker-shell';
1718
export { FilePlaceholder } from './placeholders/file-placeholder';
1819
export { GradientEditor } from './color-pickers/gradient-editor';
1920
export { HStack } from './layout/hstack';
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import clsx from 'clsx/lite';
2+
import { icons } from '../../icons';
3+
import { truncateMiddle } from '../../utilities';
4+
5+
/**
6+
* A shell for a file picker UI, handling both rich visual presentation (e.g. images) and simple file placeholders.
7+
*
8+
* @component
9+
* @param {Object} props - Component props.
10+
* @param {string} [props.url] - Current file URL.
11+
* @param {string} [props.fileName] - Current file name.
12+
* @param {ShellType} [props.type='file'] - File type icon override.
13+
* @param {JSX.Element} [props.icon] - Icon to display within the button.
14+
* @param {string} [props.className] - Classes to pass to the component.
15+
* @param {JSX.Element|JSX.Element[]} [props.noUrlContent] - Content to display if no file is selected.
16+
* @param {boolean} [props.hidden] - If `true`, the component is not rendered.
17+
*
18+
* @typedef {'image' | 'file'} ShellType
19+
*
20+
* @returns {JSX.Element} The FilePickerShell component.
21+
*
22+
* @example
23+
* <FilePickerShell
24+
* className='es:w-full'
25+
* url='#'
26+
* noUrlContent={<Button size='large'>Upload</Button>}
27+
* fileName='myfile.json'
28+
* >
29+
* <Button flat>Replace</Button>
30+
* <Button flat>Remove</Button>
31+
* </FilePickerShell>
32+
*
33+
* @example
34+
* <FilePickerShell
35+
* className='es:w-full'
36+
* url='https://picsum.photos/300/200'
37+
* noUrlContent={<Button size='large'>Upload</Button>}
38+
* type='image'
39+
* >
40+
* <Button type='glass'>Replace</Button>
41+
* <Button type='glass'>Remove</Button>
42+
* </FilePickerShell>
43+
*
44+
* @preserve
45+
*/
46+
export const FilePickerShell = (props) => {
47+
const {
48+
url,
49+
fileName,
50+
type = 'file',
51+
52+
icon = icons.file,
53+
54+
children,
55+
className,
56+
noUrlContent,
57+
58+
hidden,
59+
} = props;
60+
61+
if (hidden) {
62+
return null;
63+
}
64+
65+
const fullSizeVisual = type === 'image';
66+
67+
return (
68+
<div
69+
className={clsx(
70+
url && 'es:border es:border-secondary-300 es:bg-white es:flex es:justify-between es:rounded-2xl es:isolate es:relative es:flex-col es:gap-2 es:overflow-clip',
71+
fullSizeVisual && url && 'es:aspect-3-2 es:group',
72+
!fullSizeVisual && url && 'es:p-2 es:aspect-cinema',
73+
className,
74+
)}
75+
>
76+
{type === 'image' && url && (
77+
<img
78+
src={url}
79+
alt=''
80+
className='es:size-full es:grow es:rounded-xl'
81+
/>
82+
)}
83+
84+
{type === 'file' && url && (
85+
<div className='es:grow es:flex es:flex-col es:gap-2 es:text-sm es:items-center-safe es:justify-center-safe es:font-mono es:icon:size-6 es:rounded-xl es:bg-secondary-50 es:inset-ring es:inset-ring-secondary-100 es:icon:text-secondary-500'>
86+
{icon}
87+
<span className='es:line-clamp-1'>{truncateMiddle(fileName, 34)}</span>
88+
</div>
89+
)}
90+
91+
{url && children && (
92+
<div
93+
className={clsx(
94+
'es:grid es:auto-cols-fr es:grid-flow-col es:gap-2',
95+
fullSizeVisual &&
96+
'es:absolute es:bottom-2 es:left-2 es:right-2 es:translate-y-[125%] es:group-hover:translate-y-0 es:focus-within:translate-y-0 es:transition-transform es:ease-spring-smooth',
97+
)}
98+
>
99+
{children}
100+
</div>
101+
)}
102+
{!url && noUrlContent && <div className={clsx('es:grid es:auto-cols-fr es:grid-flow-col es:gap-2 es:p-px es:w-full')}>{noUrlContent}</div>}
103+
</div>
104+
);
105+
};

lib/index.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
--radius-7: 0.4375rem; /* 7px */
2525
--radius-10: 0.625rem; /* 10px */
2626

27+
--aspect-3-2: 3 / 2;
28+
--aspect-cinema: 21 / 9;
29+
2730
/* Theme colors */
2831
--color-secondary-50: var(--es-color-zinc-50);
2932
--color-secondary-100: var(--es-color-zinc-100);

package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eightshift/ui-components",
3-
"version": "5.3.3",
3+
"version": "5.4.0",
44
"type": "module",
55
"main": "./dist/index.js",
66
"module": "./dist/index.js",
@@ -43,7 +43,7 @@
4343
"react-dom": "^18.3.1"
4444
},
4545
"devDependencies": {
46-
"@adobe/react-spectrum": "^3.44.2",
46+
"@adobe/react-spectrum": "^3.45.0",
4747
"@dnd-kit/abstract": "^0.1.21",
4848
"@dnd-kit/core": "^6.3.1",
4949
"@dnd-kit/dom": "^0.1.21",
@@ -53,20 +53,20 @@
5353
"@dnd-kit/sortable": "^10.0.0",
5454
"@dnd-kit/utilities": "^3.2.2",
5555
"@eslint/compat": "^1.4.0",
56-
"@react-stately/collections": "^3.12.7",
56+
"@react-stately/collections": "^3.12.8",
5757
"@stylistic/eslint-plugin-js": "^4.4.1",
58-
"@tailwindcss/vite": "^4.1.13",
59-
"@types/react": "^18.3.24",
58+
"@tailwindcss/vite": "^4.1.14",
59+
"@types/react": "^18.3.26",
6060
"@types/react-dom": "^18.3.7",
6161
"@vitejs/plugin-react-swc": "^4.1.0",
62-
"@wordpress/i18n": "^6.4.0",
62+
"@wordpress/i18n": "^6.5.0",
6363
"autoprefixer": "^10.4.21",
6464
"class-variance-authority": "^0.7.1",
6565
"clsx": "^2.1.1",
6666
"css-gradient-parser": "^0.0.18",
67-
"eslint": "^9.36.0",
67+
"eslint": "^9.37.0",
6868
"eslint-config-prettier": "^10.1.8",
69-
"eslint-plugin-jsdoc": "^60.1.1",
69+
"eslint-plugin-jsdoc": "^60.8.3",
7070
"eslint-plugin-prettier": "^5.5.4",
7171
"glob": "^11.0.3",
7272
"globals": "^16.4.0",
@@ -76,23 +76,23 @@
7676
"just-is-empty": "^3.4.1",
7777
"just-kebab-case": "^4.2.0",
7878
"just-throttle": "^4.2.0",
79-
"lightningcss": "^1.30.1",
79+
"lightningcss": "^1.30.2",
8080
"postcss": "^8.5.6",
8181
"prettier": "^3.6.2",
8282
"prettier-plugin-tailwindcss": "^0.6.14",
8383
"react": "^18.3.1",
84-
"react-aria": "^3.43.2",
85-
"react-aria-components": "^1.12.2",
84+
"react-aria": "^3.44.0",
85+
"react-aria-components": "^1.13.0",
8686
"react-dom": "^18.3.1",
8787
"react-jsx-parser": "^2.4.1",
8888
"react-movable": "^3.4.1",
8989
"react-select": "^5.10.2",
90-
"react-stately": "^3.41.0",
90+
"react-stately": "^3.42.0",
9191
"svg-to-jsx-string": "^0.1.1",
92-
"tailwindcss": "^4.1.13",
92+
"tailwindcss": "^4.1.14",
9393
"tailwindcss-motion": "^1.1.1",
9494
"tailwindcss-react-aria-components": "^2.0.1",
95-
"vite": "^7.1.7",
95+
"vite": "^7.1.9",
9696
"vite-plugin-lib-inject-css": "^2.2.2"
9797
},
9898
"dependencies": {

0 commit comments

Comments
 (0)