@brilo/base-blocks 0.1.34 → 0.1.36

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.
@@ -5,6 +5,7 @@ import {
5
5
  ButtonGroup,
6
6
  PanelBody,
7
7
  PanelRow,
8
+ RangeControl,
8
9
  SelectControl,
9
10
  TextControl,
10
11
  __experimentalToggleGroupControl as ToggleGroupControl,
@@ -13,8 +14,31 @@ import {
13
14
  } from '@wordpress/components'
14
15
  import {__} from '@wordpress/i18n'
15
16
  import {useTenantColorPalette} from '~/hooks/useTenantColorPalette'
16
- import type {BaseBlockAttributes, ExcludableBaseAttr, FluidDecoration, FluidDecorationImage} from './types'
17
+ import type {
18
+ BackgroundImage,
19
+ BackgroundImageFit,
20
+ BackgroundImagePosition,
21
+ BaseBlockAttributes,
22
+ Decoration,
23
+ DecorationDepth,
24
+ DecorationImage,
25
+ DecorationPosition,
26
+ DecorationTranslate,
27
+ ExcludableBaseAttr,
28
+ FluidDecoration,
29
+ FluidDecorationImage,
30
+ } from './types'
17
31
  import {
32
+ BACKGROUND_IMAGE_FITS,
33
+ BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT,
34
+ BACKGROUND_IMAGE_POSITIONS,
35
+ DECORATION_DEPTHS,
36
+ DECORATION_MAX_WIDTH_DEFAULT,
37
+ DECORATION_MAX_WIDTH_UNITS,
38
+ DECORATION_OFFSET_DEFAULT,
39
+ DECORATION_OFFSET_UNITS,
40
+ DECORATION_POSITIONS,
41
+ DECORATION_TRANSLATES,
18
42
  FLUID_DECORATION_DEFAULT_HEIGHT,
19
43
  FLUID_DECORATION_HEIGHT_PRESETS,
20
44
  FLUID_DECORATION_UNITS,
@@ -83,6 +107,210 @@ function FluidDecorationControl({label, value, onChange}: FluidDecorationControl
83
107
  )
84
108
  }
85
109
 
110
+ interface DecorationControlProps {
111
+ value?: Decoration
112
+ onChange: (value: Decoration | undefined) => void
113
+ }
114
+
115
+ function DecorationControl({value, onChange}: DecorationControlProps) {
116
+ const img = value?.img
117
+ const position = value?.position ?? 'top-right'
118
+ const depth = value?.depth ?? 'front'
119
+ const translate = value?.translate ?? 'center'
120
+ const maxWidth = value?.maxWidth ?? DECORATION_MAX_WIDTH_DEFAULT
121
+ const offsetX = value?.offsetX ?? DECORATION_OFFSET_DEFAULT
122
+ const offsetY = value?.offsetY ?? DECORATION_OFFSET_DEFAULT
123
+
124
+ const update = (patch: Partial<Decoration>) => onChange({img, position, depth, translate, maxWidth, offsetX, offsetY, ...value, ...patch})
125
+
126
+ const handleSelect = (media: {id: number; url: string; alt: string; width?: number; height?: number}) => {
127
+ const nextImg: DecorationImage = {id: media.id, src: media.url, alt: media.alt ?? '', width: media.width, height: media.height}
128
+ update({img: nextImg})
129
+ }
130
+
131
+ const handleRemove = () => onChange(undefined)
132
+
133
+ return (
134
+ <BaseControl label={__('Obrázek', 'brilo-blocks')} __nextHasNoMarginBottom>
135
+ <MediaUploadCheck>
136
+ <MediaUpload
137
+ onSelect={handleSelect}
138
+ allowedTypes={['image']}
139
+ value={img?.id || 0}
140
+ render={({open}: {open: () => void}) => (
141
+ <PanelRow>
142
+ {img?.src && <img src={img.src} alt={img.alt} style={{maxWidth: 48, maxHeight: 48, objectFit: 'contain'}} />}
143
+ <Button onClick={open} variant="secondary" size="small">
144
+ {img?.src ? __('Změnit', 'brilo-blocks') : __('Vybrat obrázek', 'brilo-blocks')}
145
+ </Button>
146
+ {!!img?.src && (
147
+ <Button onClick={handleRemove} variant="tertiary" size="small" isDestructive>
148
+ {__('Odebrat', 'brilo-blocks')}
149
+ </Button>
150
+ )}
151
+ </PanelRow>
152
+ )}
153
+ />
154
+ </MediaUploadCheck>
155
+
156
+ {!!img?.src && (
157
+ <>
158
+ <BaseControl label={__('Pozice', 'brilo-blocks')} __nextHasNoMarginBottom>
159
+ <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4}}>
160
+ {DECORATION_POSITIONS.map((p) => (
161
+ <Button
162
+ key={p.value}
163
+ variant="secondary"
164
+ size="small"
165
+ isPressed={position === p.value}
166
+ onClick={() => update({position: p.value as DecorationPosition})}
167
+ >
168
+ {p.label}
169
+ </Button>
170
+ ))}
171
+ </div>
172
+ </BaseControl>
173
+
174
+ <ToggleGroupControl
175
+ label={__('Vrstva', 'brilo-blocks')}
176
+ value={depth}
177
+ onChange={(next) => update({depth: next as DecorationDepth})}
178
+ isBlock
179
+ >
180
+ {DECORATION_DEPTHS.map((d) => (
181
+ <ToggleGroupControlOption key={d.value} value={d.value} label={d.label} />
182
+ ))}
183
+ </ToggleGroupControl>
184
+
185
+ <ToggleGroupControl
186
+ label={__('Přesah', 'brilo-blocks')}
187
+ value={translate}
188
+ onChange={(next) => update({translate: next as DecorationTranslate})}
189
+ isBlock
190
+ >
191
+ {DECORATION_TRANSLATES.map((t) => (
192
+ <ToggleGroupControlOption key={t.value} value={t.value} label={t.label} />
193
+ ))}
194
+ </ToggleGroupControl>
195
+
196
+ <UnitControl
197
+ label={__('Max. šířka obrázku', 'brilo-blocks')}
198
+ value={maxWidth}
199
+ units={DECORATION_MAX_WIDTH_UNITS}
200
+ onChange={(next) => update({maxWidth: (next as string) ?? DECORATION_MAX_WIDTH_DEFAULT})}
201
+ />
202
+
203
+ <BaseControl
204
+ label={__('Doladění pozice', 'brilo-blocks')}
205
+ __nextHasNoMarginBottom
206
+ help={__('Ruční posun obrázku navíc k vybrané pozici.', 'brilo-blocks')}
207
+ >
208
+ <UnitControl
209
+ label={__('Posun X', 'brilo-blocks')}
210
+ value={offsetX}
211
+ units={DECORATION_OFFSET_UNITS}
212
+ onChange={(next) => update({offsetX: (next as string) ?? DECORATION_OFFSET_DEFAULT})}
213
+ />
214
+ <UnitControl
215
+ label={__('Posun Y', 'brilo-blocks')}
216
+ value={offsetY}
217
+ units={DECORATION_OFFSET_UNITS}
218
+ onChange={(next) => update({offsetY: (next as string) ?? DECORATION_OFFSET_DEFAULT})}
219
+ />
220
+ </BaseControl>
221
+ </>
222
+ )}
223
+ </BaseControl>
224
+ )
225
+ }
226
+
227
+ interface BackgroundImageControlProps {
228
+ value?: BackgroundImage
229
+ onChange: (value: BackgroundImage | undefined) => void
230
+ }
231
+
232
+ function BackgroundImageControl({value, onChange}: BackgroundImageControlProps) {
233
+ const {paletteColors} = useTenantColorPalette()
234
+ const img = value?.img
235
+ const fit = value?.fit ?? 'cover'
236
+ const position = value?.position ?? 'center center'
237
+ const overlayColor = value?.overlay?.color
238
+ const overlayOpacity = value?.overlay?.opacity ?? BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT
239
+
240
+ const update = (patch: Partial<BackgroundImage>) => onChange({img, fit, position, overlay: value?.overlay, ...value, ...patch})
241
+
242
+ const handleSelect = (media: {id: number; url: string; alt: string; width?: number; height?: number}) => {
243
+ const nextImg: FluidDecorationImage = {id: media.id, src: media.url, alt: media.alt ?? '', width: media.width, height: media.height}
244
+ update({img: nextImg})
245
+ }
246
+
247
+ const handleRemove = () => onChange(undefined)
248
+
249
+ return (
250
+ <BaseControl label={__('Obrázek pozadí', 'brilo-blocks')} __nextHasNoMarginBottom>
251
+ <MediaUploadCheck>
252
+ <MediaUpload
253
+ onSelect={handleSelect}
254
+ allowedTypes={['image']}
255
+ value={img?.id || 0}
256
+ render={({open}: {open: () => void}) => (
257
+ <PanelRow>
258
+ {img?.src && <img src={img.src} alt={img.alt} style={{maxWidth: 48, maxHeight: 48, objectFit: 'contain'}} />}
259
+ <Button onClick={open} variant="secondary" size="small">
260
+ {img?.src ? __('Změnit', 'brilo-blocks') : __('Vybrat obrázek', 'brilo-blocks')}
261
+ </Button>
262
+ {!!img?.src && (
263
+ <Button onClick={handleRemove} variant="tertiary" size="small" isDestructive>
264
+ {__('Odebrat', 'brilo-blocks')}
265
+ </Button>
266
+ )}
267
+ </PanelRow>
268
+ )}
269
+ />
270
+ </MediaUploadCheck>
271
+
272
+ {!!img?.src && (
273
+ <>
274
+ <ToggleGroupControl
275
+ label={__('Přizpůsobení', 'brilo-blocks')}
276
+ value={fit}
277
+ onChange={(next) => update({fit: next as BackgroundImageFit})}
278
+ isBlock
279
+ >
280
+ {BACKGROUND_IMAGE_FITS.map((f) => (
281
+ <ToggleGroupControlOption key={f.value} value={f.value} label={f.label} />
282
+ ))}
283
+ </ToggleGroupControl>
284
+
285
+ <SelectControl<string>
286
+ label={__('Pozice', 'brilo-blocks')}
287
+ value={position}
288
+ options={[...BACKGROUND_IMAGE_POSITIONS]}
289
+ onChange={(next) => update({position: next as BackgroundImagePosition})}
290
+ />
291
+
292
+ <BaseControl label={__('Překryv barvou', 'brilo-blocks')} __nextHasNoMarginBottom help={__('Ztmavení/zabarvení obrázku pro lepší čitelnost textu.', 'brilo-blocks')}>
293
+ <ColorPalette
294
+ colors={paletteColors}
295
+ value={overlayColor || undefined}
296
+ onChange={(next) => update({overlay: {color: next ?? undefined, opacity: overlayOpacity}})}
297
+ />
298
+ {!!overlayColor && (
299
+ <RangeControl
300
+ label={__('Krytí překryvu', 'brilo-blocks')}
301
+ value={overlayOpacity}
302
+ min={0}
303
+ max={100}
304
+ onChange={(next) => update({overlay: {color: overlayColor, opacity: next ?? BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT}})}
305
+ />
306
+ )}
307
+ </BaseControl>
308
+ </>
309
+ )}
310
+ </BaseControl>
311
+ )
312
+ }
313
+
86
314
  type SpacingPosition = BaseBlockAttributes['spacing']['position']
87
315
  type SpacingSize = BaseBlockAttributes['spacing']['size']
88
316
 
@@ -93,7 +321,7 @@ interface BaseControlsProps {
93
321
  }
94
322
 
95
323
  export function BaseControls({attributes, setAttributes, exclude}: BaseControlsProps) {
96
- const {id, spacing, background, textColor, decorations} = attributes
324
+ const {id, spacing, background, textColor, decoration, decorations, backgroundImage} = attributes
97
325
  const {paletteColors, resolveHex} = useTenantColorPalette()
98
326
  const fluid = decorations?.fluid ?? {}
99
327
 
@@ -143,6 +371,18 @@ export function BaseControls({attributes, setAttributes, exclude}: BaseControlsP
143
371
  </PanelBody>
144
372
  )}
145
373
 
374
+ {!exclude.includes('backgroundImage') && (
375
+ <PanelBody title={__('Obrázek pozadí', 'brilo-blocks')} initialOpen={false}>
376
+ <BackgroundImageControl value={backgroundImage} onChange={(value) => setAttributes({backgroundImage: value ?? {}})} />
377
+ </PanelBody>
378
+ )}
379
+
380
+ {!exclude.includes('decoration') && (
381
+ <PanelBody title={__('Dekorace', 'brilo-blocks')} initialOpen={false}>
382
+ <DecorationControl value={decoration} onChange={(value) => setAttributes({decoration: value ?? {}})} />
383
+ </PanelBody>
384
+ )}
385
+
146
386
  {!exclude.includes('decorations') && (
147
387
  <PanelBody title={__('Fluidní dekorace', 'brilo-blocks')} initialOpen={false}>
148
388
  <FluidDecorationControl label={__('Nahoře', 'brilo-blocks')} value={fluid.top} onChange={(value) => setFluid('top', value)} />
@@ -1,5 +1,6 @@
1
1
  import {InspectorControls, useBlockProps, useInnerBlocksProps} from '@wordpress/block-editor'
2
2
  import type {BlockEditProps} from '@wordpress/blocks'
3
+ import type {CSSProperties} from 'react'
3
4
  import type {BaseBlockAttributes, BaseSectionOptions} from './types'
4
5
  import {BaseControls} from './controls'
5
6
 
@@ -8,13 +9,28 @@ export function createBaseSectionEdit<TExtra extends Record<string, unknown> = R
8
9
 
9
10
  return function Edit(props: BlockEditProps<BaseBlockAttributes & TExtra>) {
10
11
  const {attributes, setAttributes} = props
11
- const {background, textColor, gradient} = attributes as BaseBlockAttributes & {gradient?: string}
12
+ const {background, textColor, gradient, backgroundImage} = attributes as BaseBlockAttributes & {gradient?: string}
13
+ const bgImageSrc = backgroundImage?.img?.src
14
+ // A plain background-color/gradient always paints behind an image, so it can never tint it. To preview a
15
+ // translucent color/gradient sitting on top of the image (matching frontend), stack it as its own layer
16
+ // in front of the `url(...)` layer instead of setting backgroundColor separately.
17
+ const tintLayer = background ? `linear-gradient(${background}, ${background})` : gradient
12
18
  const blockProps = useBlockProps({
13
19
  className: options.blockClassName,
14
20
  style: {
15
- ...(background ? {backgroundColor: background, padding: '16px'} : {}),
16
- ...(gradient ? {background: gradient, padding: '16px'} : {}),
21
+ position: 'relative' as CSSProperties['position'],
22
+ ...(background && !bgImageSrc ? {backgroundColor: background, padding: '16px'} : {}),
23
+ ...(gradient && !bgImageSrc ? {background: gradient, padding: '16px'} : {}),
17
24
  ...(textColor ? {color: textColor} : {}),
25
+ ...(bgImageSrc
26
+ ? {
27
+ backgroundImage: [tintLayer, `url(${bgImageSrc})`].filter(Boolean).join(', '),
28
+ backgroundSize: [tintLayer ? '100% 100%' : null, backgroundImage?.fit ?? 'cover'].filter(Boolean).join(', '),
29
+ backgroundPosition: [tintLayer ? 'center center' : null, backgroundImage?.position ?? 'center center'].filter(Boolean).join(', '),
30
+ backgroundRepeat: 'no-repeat',
31
+ padding: '16px',
32
+ }
33
+ : {}),
18
34
  },
19
35
  })
20
36
  const innerBlocksProps = useInnerBlocksProps({className: options.containerClassName}, {
@@ -15,6 +15,41 @@ export interface FluidDecoration {
15
15
  height: string
16
16
  }
17
17
 
18
+ export type DecorationImage = FluidDecorationImage
19
+
20
+ export type DecorationPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center-left' | 'center-right'
21
+ export type DecorationDepth = 'front' | 'under'
22
+ export type DecorationTranslate = 'in' | 'center' | 'out'
23
+
24
+ export interface Decoration {
25
+ img?: DecorationImage
26
+ position?: DecorationPosition
27
+ depth?: DecorationDepth
28
+ translate?: DecorationTranslate
29
+ maxWidth?: string
30
+ offsetX?: string
31
+ offsetY?: string
32
+ }
33
+
34
+ export type BackgroundImageFit = 'cover' | 'contain'
35
+
36
+ export type BackgroundImagePosition =
37
+ | 'left top' | 'center top' | 'right top'
38
+ | 'left center' | 'center center' | 'right center'
39
+ | 'left bottom' | 'center bottom' | 'right bottom'
40
+
41
+ export interface BackgroundImageOverlay {
42
+ color?: string
43
+ opacity?: number
44
+ }
45
+
46
+ export interface BackgroundImage {
47
+ img?: FluidDecorationImage
48
+ fit?: BackgroundImageFit
49
+ position?: BackgroundImagePosition
50
+ overlay?: BackgroundImageOverlay
51
+ }
52
+
18
53
  export interface BaseBlockAttributes extends Record<string, unknown> {
19
54
  id: string
20
55
  spacing: {
@@ -23,15 +58,17 @@ export interface BaseBlockAttributes extends Record<string, unknown> {
23
58
  }
24
59
  background: string
25
60
  textColor: string
61
+ decoration: Decoration
26
62
  decorations: {
27
63
  fluid?: {
28
64
  top?: FluidDecoration
29
65
  bottom?: FluidDecoration
30
66
  }
31
67
  }
68
+ backgroundImage: BackgroundImage
32
69
  }
33
70
 
34
- export type ExcludableBaseAttr = 'spacing' | 'background' | 'id' | 'decorations'
71
+ export type ExcludableBaseAttr = 'spacing' | 'background' | 'id' | 'decorations' | 'decoration' | 'backgroundImage'
35
72
 
36
73
  export interface BaseSectionOptions<TExtra extends Record<string, unknown> = Record<string, never>> extends BriloBlockType {
37
74
  supports?: BlockConfiguration['supports']