barbican-reset 3.32.0 → 3.34.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 (38) hide show
  1. package/components/BrConfirmDone.vue +19 -13
  2. package/components/BrConfirmEmail.vue +19 -18
  3. package/components/BrDetails.vue +6 -2
  4. package/components/BrFormCheckbox.vue +37 -3
  5. package/components/BrFormLabel.vue +2 -1
  6. package/components/BrFormRadio.vue +20 -2
  7. package/components/BrOverlay.vue +13 -6
  8. package/components/BrTableCell.vue +50 -0
  9. package/components/BrTableHeader.vue +1 -5
  10. package/components/BrTableRow.vue +5 -0
  11. package/components/BrWrap.vue +6 -0
  12. package/index.js +4 -0
  13. package/package.json +1 -1
  14. package/scripts/animations/confirm.js +79 -0
  15. package/scripts/helpers/constrainTabbing.js +40 -0
  16. package/scripts/helpers/logObject.js +3 -10
  17. package/scripts/helpers.js +2 -1
  18. package/scss/_atomic.scss +5 -0
  19. package/scss/_br-form-fieldset.scss +2 -2
  20. package/scss/_br-form-label.scss +3 -0
  21. package/scss/_br-form-radio.scss +17 -1
  22. package/scss/_br-table-cell.scss +75 -0
  23. package/scss/_br-table-header.scss +3 -49
  24. package/scss/_br-table-row.scss +24 -0
  25. package/scss/_variables.scss +3 -2
  26. package/scss/atomic/_font-sizes.scss +6 -0
  27. package/scss/atomic/_margins.scss +12 -0
  28. package/scss/atomic/_widths.scss +43 -0
  29. package/scss/index.scss +3 -0
  30. package/scss/mixins/_br-card.scss +3 -6
  31. package/scss/mixins/_br-form-radio.scss +3 -3
  32. package/scss/mixins/_br-wrap.scss +21 -18
  33. package/scss/mixins/_font-size.scss +9 -0
  34. package/scss/mixins/buttons/custom/_renew-membership.scss +2 -0
  35. package/scss/mixins/index.scss +1 -0
  36. package/scss/mixins/input/_checkbox.scss +20 -3
  37. package/animations/confirm.js +0 -61
  38. package/animations/index.js +0 -8
@@ -1,26 +1,32 @@
1
1
  <template>
2
- <br-card confirm done>
2
+ <br-card class="confirm-done">
3
3
  <done-icon />
4
- <br-card-title>Done</br-card-title>
5
- <br-card-text v-if="$slots.default">
4
+ <br-card-title class="title">Done</br-card-title>
5
+ <br-card-text v-if="$slots.default" class="margin-top-1">
6
6
  <slot />
7
7
  </br-card-text>
8
8
  </br-card>
9
9
  </template>
10
10
 
11
- <script>
11
+ <script setup>
12
+ import { onMounted } from 'vue'
13
+ import { animateDone } from '#scripts/animations/confirm'
14
+
12
15
  import BrCard from '#components/BrCard.vue'
13
16
  import BrCardTitle from '#components/BrCardTitle.vue'
14
17
  import BrCardText from '#components/BrCardText.vue'
15
-
16
- import Animations from '#animations/index'
17
18
  import DoneIcon from '#icons/confirm/done.vue'
18
19
 
19
- export default {
20
- mixins: [Animations],
21
- components: { BrCard, BrCardTitle, BrCardText, DoneIcon },
22
- mounted() {
23
- this.animateDone()
24
- },
25
- }
20
+ onMounted(() => animateDone())
26
21
  </script>
22
+
23
+ <style scoped>
24
+ .confirm-done {
25
+ max-width: var(--width-title);
26
+ text-align: center;
27
+ }
28
+
29
+ .title {
30
+ text-align: center;
31
+ }
32
+ </style>
@@ -1,33 +1,34 @@
1
1
  <template>
2
- <br-card confirm email>
2
+ <br-card class="confirm-email">
3
3
  <email-icon />
4
- <br-card-title v-if="$slots.title">
4
+ <br-card-title v-if="$slots.title" class="title">
5
5
  <slot name="title" />
6
6
  </br-card-title>
7
- <br-card-text v-if="$slots.default">
7
+ <br-card-text v-if="$slots.default" class="margin-top-1">
8
8
  <slot />
9
9
  </br-card-text>
10
10
  </br-card>
11
11
  </template>
12
12
 
13
- <script>
13
+ <script setup>
14
+ import { onMounted } from 'vue'
15
+ import { animateEmail } from '#scripts/animations/confirm'
16
+
14
17
  import BrCard from '#components/BrCard.vue'
15
18
  import BrCardTitle from '#components/BrCardTitle.vue'
16
19
  import BrCardText from '#components/BrCardText.vue'
17
-
18
- import Animations from '#animations/index'
19
20
  import EmailIcon from '#icons/confirm/email.vue'
20
21
 
21
- export default {
22
- mixins: [Animations],
23
- components: {
24
- BrCard,
25
- BrCardTitle,
26
- BrCardText,
27
- EmailIcon,
28
- },
29
- mounted() {
30
- this.animateEmail()
31
- },
32
- }
22
+ onMounted(() => animateEmail())
33
23
  </script>
24
+
25
+ <style scoped>
26
+ .confirm-email {
27
+ max-width: var(--width-title);
28
+ text-align: center;
29
+ }
30
+
31
+ .title {
32
+ text-align: center;
33
+ }
34
+ </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="br-details">
3
- <br-button variant="summary" @click="update">
3
+ <br-button variant="summary" @click="update" :data-test="'toggle-' + label">
4
4
  <div class="wrap-title">
5
5
  <slot name="title">Title goes here</slot>
6
6
  </div>
@@ -20,6 +20,10 @@ const props = defineProps({
20
20
  type: Boolean,
21
21
  default: false,
22
22
  },
23
+ label: {
24
+ type: String,
25
+ default: 'details',
26
+ },
23
27
  })
24
28
 
25
29
  const update = () => emit('update', { open: !props.open })
@@ -50,6 +54,6 @@ const update = () => emit('update', { open: !props.open })
50
54
  }
51
55
 
52
56
  .wrap-content.open {
53
- margin-top: var(--margin-xl);
57
+ margin-top: var(--margin-xxl);
54
58
  }
55
59
  </style>
@@ -1,14 +1,22 @@
1
+ <!--
2
+ @component BrFormCheckbox
3
+ @description A checkbox form field that wraps BrFormInput (type="checkbox") inside a BrFormLabel.
4
+ Supports success/error states, disabled state, block-level layout, and an optional label slot.
5
+ Non-prop attributes (e.g. name, value) are forwarded directly to the underlying input element.
6
+ Any classes passed to this component are merged onto the root wrapper div.
7
+ -->
1
8
  <template>
2
- <div :class="['br-form-checkbox', { block }]">
9
+ <div :class="['br-form-checkbox', { block }, $attrs.class]">
3
10
  <br-form-label
4
11
  :class="[{ success }, { error }]"
5
12
  :disabled="disabled"
13
+ :returns="returns"
6
14
  :id="generateID">
7
15
  <br-form-input
8
16
  :class="[{ success }, { error }]"
9
17
  :disabled="disabled"
18
+ v-bind="inputAttrs"
10
19
  :id="generateID"
11
- v-bind="$attrs"
12
20
  type="checkbox"
13
21
  :error="error" />
14
22
  <span v-if="$slots.default" class="label-text">
@@ -19,21 +27,47 @@
19
27
  </template>
20
28
 
21
29
  <script setup>
22
- import { computed } from 'vue'
30
+ import { computed, useAttrs } from 'vue'
23
31
  import formatKebabCase from '#helpers/formatKebabCase'
24
32
  import BrFormLabel from '#components/BrFormLabel.vue'
25
33
  import BrFormInput from '#components/BrFormInput.vue'
26
34
 
35
+ /** All fallthrough attributes from the parent. */
36
+ const attrs = useAttrs()
37
+
38
+ /**
39
+ * Attrs forwarded to the inner input, with `class` removed so it doesn't
40
+ * double-apply (class is already merged onto the root div via $attrs.class).
41
+ *
42
+ * @type {import('vue').ComputedRef<Record<string, unknown>>}
43
+ */
44
+ const inputAttrs = computed(() => {
45
+ const { class: _, ...rest } = attrs
46
+ return rest
47
+ })
48
+
49
+ /**
50
+ * Disable automatic attribute inheritance so $attrs can be manually
51
+ * forwarded to the inner input element rather than the root div.
52
+ */
27
53
  defineOptions({
28
54
  inheritAttrs: false,
29
55
  })
30
56
 
57
+ /**
58
+ * @prop {string} [id] - Explicit id for the input/label pair. Falls back to a random id.
59
+ * @prop {boolean} [disabled] - Disables the checkbox input and label.
60
+ * @prop {boolean} [success] - Applies success styling to the label and input.
61
+ * @prop {boolean} [error] - Applies error styling to the label and input.
62
+ * @prop {boolean} [block] - Makes the checkbox wrapper display as a block-level element.
63
+ */
31
64
  const props = defineProps({
32
65
  id: String,
33
66
  disabled: Boolean,
34
67
  success: Boolean,
35
68
  error: Boolean,
36
69
  block: Boolean,
70
+ returns: Boolean,
37
71
  })
38
72
 
39
73
  // @description Returns a string for the "id" attribute
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <component
3
- :class="['br-form-label', { disabled }]"
3
+ :class="['br-form-label', { disabled }, { returns }]"
4
4
  :is="!disabled ? 'label' : 'div'"
5
5
  :for="!disabled ? id : null">
6
6
  <slot />
@@ -14,6 +14,7 @@ const props = defineProps({
14
14
  disabled: Boolean,
15
15
  required: Boolean,
16
16
  optional: Boolean,
17
+ returns: Boolean,
17
18
  id: String,
18
19
  })
19
20
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="br-form-radio">
2
+ <div :class="generateClassnames">
3
3
  <br-form-label
4
4
  :class="[{ success }, { error }]"
5
5
  :disabled="disabled"
@@ -11,7 +11,7 @@
11
11
  v-bind="$attrs"
12
12
  :error="error"
13
13
  type="radio" />
14
- <span class="label-text">
14
+ <span v-if="$slots.default" class="label-text">
15
15
  <slot />
16
16
  </span>
17
17
  </br-form-label>
@@ -25,6 +25,20 @@ import formatKebabCase from '#helpers/formatKebabCase'
25
25
  import BrFormLabel from '#components/BrFormLabel.vue'
26
26
  import BrFormInput from '#components/BrFormInput.vue'
27
27
 
28
+ const generateClassnames = computed(() => {
29
+ let classnames = ['br-form-radio']
30
+
31
+ if (props.table) {
32
+ classnames.push('table')
33
+ }
34
+
35
+ if (!props.table) {
36
+ classnames.push('default')
37
+ }
38
+
39
+ return classnames
40
+ })
41
+
28
42
  defineOptions({
29
43
  inheritAttrs: false,
30
44
  })
@@ -45,6 +59,10 @@ const props = defineProps({
45
59
  default: false,
46
60
  type: Boolean,
47
61
  },
62
+ table: {
63
+ default: false,
64
+ type: Boolean,
65
+ },
48
66
  })
49
67
 
50
68
  // @description Returns a string for the "id" attribute
@@ -10,24 +10,31 @@
10
10
  </template>
11
11
 
12
12
  <script setup>
13
- import { onMounted, onUnmounted } from 'vue';
14
- import BrButton from "#components/BrButton.vue";
15
- import CloseIcon from "#icons/close.vue";
13
+ import { onMounted, onUnmounted } from 'vue'
14
+ import { constrainTabbing } from '#scripts/helpers'
15
+ import BrButton from '#components/BrButton.vue'
16
+ import CloseIcon from '#icons/close.vue'
16
17
 
17
- const emit = defineEmits(["closeOverlay"]);
18
+ const emit = defineEmits(['closeOverlay'])
19
+
20
+ function constrainOverlayPadding() {
21
+ let $overlay = document.querySelector('.br-overlay-wrap')
22
+ constrainTabbing($overlay)
23
+ }
18
24
 
19
25
  function listenForKeys({ key }) {
20
26
  if (key == 'Escape') {
21
- emit('closeOverlay')
27
+ emit('closeOverlay')
22
28
  }
23
29
  }
24
30
 
25
31
  onMounted(() => {
26
32
  document.addEventListener('keydown', listenForKeys)
33
+ document.addEventListener('keydown', constrainOverlayPadding)
27
34
  })
28
35
 
29
36
  onUnmounted(() => {
30
37
  document.removeEventListener('keydown', listenForKeys)
38
+ document.removeEventListener('keydown', constrainOverlayPadding)
31
39
  })
32
40
  </script>
33
-
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div :class="generateClassnames">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { computed } from 'vue'
9
+
10
+ const generateClassnames = computed(() => {
11
+ let classnames = ['br-table-cell']
12
+
13
+ if (props.width) {
14
+ classnames.push('width-' + props.width)
15
+ }
16
+
17
+ if (props.right) {
18
+ classnames.push('align-right')
19
+ }
20
+
21
+ if (props.center) {
22
+ classnames.push('align-center')
23
+ }
24
+
25
+ if (props.radio) {
26
+ classnames.push('radio')
27
+ }
28
+
29
+ if (props.returns) {
30
+ classnames.push('returns')
31
+ }
32
+
33
+ if (!props.radio && !props.returns) {
34
+ classnames.push('default')
35
+ }
36
+
37
+ return classnames
38
+ })
39
+
40
+ const props = defineProps({
41
+ width: {
42
+ type: String,
43
+ default: 'stretch',
44
+ },
45
+ returns: Boolean,
46
+ right: Boolean,
47
+ center: Boolean,
48
+ radio: Boolean,
49
+ })
50
+ </script>
@@ -1,9 +1,5 @@
1
1
  <template>
2
2
  <div class="br-table-header">
3
- <div class="br-table-header-title">
4
- <slot />
5
- </div>
6
- <div class="br-table-header-title yes">Yes</div>
7
- <div class="br-table-header-title no">No</div>
3
+ <slot />
8
4
  </div>
9
5
  </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="br-table-row">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -17,11 +17,13 @@ const generateComponentClasses = computed(() => {
17
17
  if (props.cluster) {
18
18
  classnames.push('cluster')
19
19
  classnames.push('align-' + props.align)
20
+ classnames.push('size-' + props.size)
20
21
  }
21
22
 
22
23
  if (props.orders) {
23
24
  classnames.push('orders')
24
25
  classnames.push('align-' + props.align)
26
+ classnames.push('size-' + props.size)
25
27
  }
26
28
 
27
29
  if (props.preferences) {
@@ -44,5 +46,9 @@ const props = defineProps({
44
46
  type: String,
45
47
  default: 'middle',
46
48
  },
49
+ size: {
50
+ type: String,
51
+ default: 'xs',
52
+ },
47
53
  })
48
54
  </script>
package/index.js CHANGED
@@ -45,7 +45,9 @@ import BrOverlay from '#components/BrOverlay.vue'
45
45
  import BrLoader from '#components/BrLoader.vue'
46
46
  import BrSkiplink from '#components/BrSkiplink.vue'
47
47
  import BrStatusBars from '#components/BrStatusBars.vue'
48
+ import BrTableCell from '#components/BrTableCell.vue'
48
49
  import BrTableHeader from '#components/BrTableHeader.vue'
50
+ import BrTableRow from '#components/BrTableRow.vue'
49
51
  import BrWrap from '#components/BrWrap.vue'
50
52
 
51
53
  export {
@@ -89,6 +91,8 @@ export {
89
91
  BrOverlay,
90
92
  BrSkiplink,
91
93
  BrStatusBars,
94
+ BrTableCell,
92
95
  BrTableHeader,
96
+ BrTableRow,
93
97
  BrWrap,
94
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "barbican-reset",
3
- "version": "3.32.0",
3
+ "version": "3.34.0",
4
4
  "description": "Shared design system for Barbican projects, providing SCSS utilities, animations, icons, Vue components, and JS helpers for consistent styling and behaviour.",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -0,0 +1,79 @@
1
+ import gsap from 'gsap'
2
+
3
+ let defaults = {
4
+ duration: 0.6,
5
+ ease: 'power1.in',
6
+ transformOrigin: 'center',
7
+ }
8
+
9
+ let fadeIn = {
10
+ duration: 0.3,
11
+ opacity: 0,
12
+ delay: 0.3,
13
+ }
14
+
15
+ let explode = {
16
+ ease: 'power1.out',
17
+ opacity: 0,
18
+ scale: 1.5,
19
+ }
20
+
21
+ export function animateDone() {
22
+ let $card = document.querySelector('.br-card.confirm-done')
23
+ let $title = $card.querySelector('.br-card-title')
24
+ let $outline = $card.querySelector('.outline')
25
+ let $arrow = $card.querySelector('.arrow')
26
+ let $tick = $card.querySelector('.tick')
27
+
28
+ let tl = gsap.timeline({ defaults })
29
+
30
+ let $clone = $title.cloneNode(true)
31
+
32
+ $clone.classList.add('clone')
33
+
34
+ $title.after($clone)
35
+
36
+ let cloneHeight = $card.querySelector('.clone').offsetHeight
37
+
38
+ $clone.style.marginTop = `-${cloneHeight}px`
39
+
40
+ // prettier-ignore
41
+ tl.set($arrow, { opacity: 0 })
42
+ .from($title, fadeIn)
43
+ .set($clone, { opacity: 0.4 })
44
+ .from($outline, { drawSVG: '0%' }, 'start')
45
+ .from($tick, { drawSVG: '0%', duration: 0.3 }, 'start')
46
+ .to($clone, explode, 'start')
47
+ .set($arrow, { opacity: 1 })
48
+ .from($arrow, { x: -6, ease: 'power1.out' })
49
+ }
50
+
51
+ export function animateEmail() {
52
+ let $card = document.querySelector('.br-card.confirm-email')
53
+ let $title = $card.querySelector('.br-card-title')
54
+ let $outline = $card.querySelector('.outline')
55
+ let $arrow = $card.querySelector('.arrow')
56
+ let $fold = $card.querySelector('.fold')
57
+
58
+ let tl = gsap.timeline({ defaults })
59
+
60
+ let $clone = $title.cloneNode(true)
61
+
62
+ $clone.classList.add('clone')
63
+
64
+ $title.after($clone)
65
+
66
+ let cloneHeight = $card.querySelector('.clone').offsetHeight
67
+
68
+ $clone.style.marginTop = `-${cloneHeight}px`
69
+
70
+ // prettier-ignore
71
+ tl.set($arrow, { opacity: 0 })
72
+ .from($title, fadeIn)
73
+ .set($clone, { opacity: 0.4 })
74
+ .from($outline, { drawSVG: '0%' }, 'start')
75
+ .from($fold, { drawSVG: '0%', duration: 0.3 }, 'start')
76
+ .to($clone, explode, 'start')
77
+ .set($arrow, { opacity: 1 })
78
+ .from($arrow, { x: -6, ease: 'power1.out' })
79
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Constrains keyboard tab focus to within a given DOM element, creating a focus trap.
3
+ * When the user tabs past the last focusable element, focus wraps to the first,
4
+ * and vice versa when shift-tabbing past the first.
5
+ *
6
+ * Useful for modal dialogs and overlays where focus should not escape to the
7
+ * rest of the page while the element is active.
8
+ *
9
+ * @param {HTMLElement} element - The container element to trap focus within.
10
+ * @returns {void}
11
+ *
12
+ * @example
13
+ * constrainTabbing(document.getElementById('modal'))
14
+ */
15
+ export default function (element) {
16
+ let focusableEls = element.querySelectorAll('a[href]:not([disabled], [rel=noopener]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])')
17
+ let firstFocusableEl = focusableEls[0]
18
+ let lastFocusableEl = focusableEls[focusableEls.length - 1]
19
+ let KEYCODE_TAB = 9
20
+
21
+ element.addEventListener('keydown', function (event) {
22
+ let { key, keyCode, shiftKey } = event
23
+
24
+ var isTabPressed = key === 'Tab' || keyCode === KEYCODE_TAB
25
+
26
+ if (!isTabPressed) return
27
+
28
+ if (shiftKey) {
29
+ if (document.activeElement === firstFocusableEl) {
30
+ lastFocusableEl.focus()
31
+ event.preventDefault()
32
+ }
33
+ } else {
34
+ if (document.activeElement === lastFocusableEl) {
35
+ firstFocusableEl.focus()
36
+ event.preventDefault()
37
+ }
38
+ }
39
+ })
40
+ }
@@ -1,17 +1,10 @@
1
1
  /**
2
- * Logs an entire object to the console, including deeply nested children.
3
- * Uses `util.inspect` to avoid truncation of complex or deeply nested structures.
2
+ * Logs a value to the console as pretty-printed JSON.
4
3
  *
5
- * @param {*} target - The value to inspect and log.
4
+ * @param {*} target - The value to log.
6
5
  * @returns {void}
7
- *
8
- * @example
9
- * logObject({ complicated: 'object', with: { child: 'values' } })
10
6
  */
11
7
 
12
- import util from 'util'
13
-
14
8
  export default function (target) {
15
- let options = { showHidden: false, depth: null, colors: true }
16
- console.log(util.inspect(target, options))
9
+ return console.log(JSON.stringify(target, null, 2))
17
10
  }
@@ -1,5 +1,6 @@
1
1
  import compareArrays from '#helpers/compareArrays'
2
+ import constrainTabbing from '#helpers/constrainTabbing'
2
3
  import formatKebabCase from '#helpers/formatKebabCase'
3
4
  import logObject from '#helpers/logObject'
4
5
 
5
- export { compareArrays, formatKebabCase, logObject }
6
+ export { compareArrays, constrainTabbing, formatKebabCase, logObject }
package/scss/_atomic.scss CHANGED
@@ -9,6 +9,7 @@
9
9
  @use "atomic/min-widths";
10
10
  @use "atomic/paddings";
11
11
  @use "atomic/text-aligns";
12
+ @use "atomic/widths";
12
13
 
13
14
  .border-radius-lg {
14
15
  border-radius: var(--border-radius-lg);
@@ -48,4 +49,8 @@
48
49
 
49
50
  .display-inline-block {
50
51
  display: inline-block;
52
+ }
53
+
54
+ .word-wrap-break-word {
55
+ word-wrap: break-word;
51
56
  }
@@ -23,14 +23,14 @@ fieldset,
23
23
 
24
24
  @include small-up {
25
25
  .fieldset.table {
26
- grid-template-columns: auto calc(var(--width-column) * 2);
27
26
  margin-top: var(--margin-sm);
28
27
  border-radius: 0;
29
- display: grid;
28
+ display: flex;
30
29
  padding: 0;
31
30
  }
32
31
 
33
32
  .fieldset .wrap-legend {
33
+ width: stretch;
34
34
  margin-top: 0;
35
35
  }
36
36
 
@@ -0,0 +1,3 @@
1
+ .br-form-label {
2
+ font-weight: bold;
3
+ }
@@ -1,5 +1,21 @@
1
1
  @use "mixins/input" as *;
2
+ @use "mixins/breakpoints" as *;
2
3
 
3
- .br-form-radio {
4
+ .br-form-radio.default {
4
5
  @include br-form-radio;
6
+ }
7
+
8
+ .br-form-radio.table {
9
+ @include br-form-radio;
10
+
11
+ @include medium-up {
12
+ .br-form-label {
13
+ padding: var(--padding-sm);
14
+ border-radius: 50%;
15
+ }
16
+
17
+ .label-text {
18
+ display: none;
19
+ }
20
+ }
5
21
  }
@@ -0,0 +1,75 @@
1
+ @use "mixins/breakpoints" as *;
2
+ @use "mixins/br-wrap" as *;
3
+
4
+ @mixin br-table-cell--default {
5
+ @include medium-up {
6
+ padding: var(--padding-md);
7
+
8
+ &:not(:first-child) {
9
+ border-left-color: var(--color-black-25-lighten);
10
+ border-left-width: var(--border-width-sm);
11
+ border-left-style: solid;
12
+ }
13
+
14
+ &.align-right {
15
+ text-align: right;
16
+ }
17
+
18
+ &.align-center {
19
+ text-align: center;
20
+ }
21
+
22
+ &:not(.width-stretch) {
23
+ flex-shrink: 0;
24
+ }
25
+
26
+ >label {
27
+ display: none;
28
+ }
29
+ }
30
+
31
+ @include medium-down {
32
+ margin: var(--margin-sm);
33
+ }
34
+ }
35
+
36
+ @mixin br-table-cell--radio {
37
+ @include br-table-cell--default;
38
+
39
+ @include medium-down {
40
+ &:not(.width-stretch) {
41
+ display: inline-block;
42
+ width: initial;
43
+ }
44
+ }
45
+ }
46
+
47
+ @mixin br-table-cell--returns {
48
+ @include br-table-cell--default;
49
+
50
+ @include medium-up {
51
+ padding: 0;
52
+ }
53
+
54
+ @include medium-down {
55
+ border-bottom-color: var(--color-black-25-lighten);
56
+ border-bottom-width: var(--border-width-sm);
57
+ border-bottom-style: dashed;
58
+ margin-right: -0.5rem;
59
+ margin-left: -0.5rem;
60
+ margin-bottom: 1rem;
61
+ margin-top: -0.5rem;
62
+ }
63
+ }
64
+
65
+ .br-table-cell.default {
66
+ @include br-table-cell--default;
67
+ }
68
+
69
+ .br-table-cell.radio {
70
+ @include br-table-cell--radio;
71
+ }
72
+
73
+ .br-table-cell.returns {
74
+ @include br-table-cell--returns;
75
+ }
@@ -3,58 +3,12 @@
3
3
  .br-table-header {
4
4
  border-color: var(--color-black-25-lighten);
5
5
  border-width: var(--border-width-sm);
6
- margin-bottom: var(--margin-sm);
6
+ margin-bottom: var(--margin-md);
7
7
  border-style: solid;
8
8
  display: none;
9
9
 
10
- @include small-up {
10
+ @include medium-up {
11
11
  background-color: var(--color-black-5-lighten);
12
- grid-template-columns: auto repeat(2, 5rem);
13
- display: grid;
12
+ display: flex;
14
13
  }
15
- }
16
-
17
- .br-table-header-title {
18
- padding: var(--padding-md);
19
- font-weight: bold;
20
- }
21
-
22
- .br-table-header-title+.br-table-header-title {
23
- border-left-color: var(--color-black-25-lighten);
24
- border-left-width: var(--border-width-sm);
25
- border-left-style: solid;
26
- }
27
-
28
- .br-table-header-title.yes,
29
- .br-table-header-title.no {
30
- text-align: center;
31
- }
32
-
33
- .br-table-rows {
34
- list-style: none;
35
- padding: 0;
36
- margin: 0;
37
- }
38
-
39
- .br-table-row {
40
- border-color: var(--color-black-25-lighten);
41
- border-width: var(--border-width-sm);
42
- border-style: solid;
43
-
44
- @include small-up {
45
- grid-template-columns: auto repeat(2, 5rem);
46
- display: grid;
47
- }
48
- }
49
-
50
- .br-table-row div {
51
- border-color: var(--color-black-25-lighten);
52
- border-width: var(--border-width-sm);
53
- padding: var(--padding-md);
54
- }
55
-
56
- .br-table-row div+div {
57
- border-left-color: var(--color-black-25-lighten);
58
- border-left-width: var(--border-width-sm);
59
- border-left-style: solid;
60
14
  }
@@ -0,0 +1,24 @@
1
+ @use "mixins/breakpoints" as *;
2
+
3
+ @mixin br-table-row--default {
4
+ border-color: var(--color-black-25-lighten);
5
+ border-width: var(--border-width-sm);
6
+ border-style: solid;
7
+
8
+ &:not(:last-child) {
9
+ margin-bottom: var(--margin-md);
10
+ }
11
+
12
+ @include medium-up {
13
+ display: flex;
14
+ }
15
+
16
+ @include medium-down {
17
+ border-radius: var(--border-radius-lg);
18
+ padding: var(--padding-sm);
19
+ }
20
+ }
21
+
22
+ .br-table-row {
23
+ @include br-table-row--default;
24
+ }
@@ -344,6 +344,7 @@
344
344
  --line-height-xs: 1;
345
345
  --line-height-sm: 1.15;
346
346
  --line-height-md: 1.35;
347
+ --line-height-lg: 1.5;
347
348
 
348
349
  // Fixed line height for button/menu items (`38px`).
349
350
  --line-height-button-menu: 2.375rem;
@@ -432,8 +433,8 @@
432
433
  --width-icon: 10rem;
433
434
  // Title column width (`320px`).
434
435
  --width-title: 20rem;
435
- // Extra-small layout max-width (`384px`).
436
- --width-layout-xs: 24rem;
436
+ // Extra-small layout max-width (`400px`).
437
+ --width-layout-xs: 25rem;
437
438
  // Small layout max-width (`800px`).
438
439
  --width-layout-sm: 50rem;
439
440
  // Medium layout max-width (`960px`).
@@ -1,3 +1,5 @@
1
+ @use "../mixins/font-size" as *;
2
+
1
3
  $iterations: (
2
4
  "lg": var(--font-size-lg),
3
5
  "h1": var(--font-size-h1),
@@ -12,4 +14,8 @@ $iterations: (
12
14
  .font-size-#{$name} {
13
15
  font-size: #{$value};
14
16
  }
17
+ }
18
+
19
+ .font-size-subtitle {
20
+ @include font-size-subtitle;
15
21
  }
@@ -20,4 +20,16 @@ $iterations: (
20
20
  #{$margin}: #{$value}rem;
21
21
  }
22
22
  }
23
+ }
24
+
25
+ @each $name, $value in $iterations {
26
+ .margin-top-bottom-#{$name} {
27
+ margin-bottom: #{$value}rem;
28
+ margin-top: #{$value}rem;
29
+ }
30
+
31
+ .margin-left-right-#{$name} {
32
+ margin-right: #{$value}rem;
33
+ margin-left: #{$value}rem;
34
+ }
23
35
  }
@@ -0,0 +1,43 @@
1
+ $iterations: (
2
+ "0": 0,
3
+ "0-5": 0.5,
4
+ "1": 1,
5
+ "1-5": 1.5,
6
+ "2": 2,
7
+ "2-5": 2.5,
8
+ "3": 3,
9
+ "3-5": 3.5,
10
+ "4": 4,
11
+ "4-5": 4.5,
12
+ "5": 5,
13
+ "5-5": 5.5,
14
+ "6": 6,
15
+ "6-5": 6.5,
16
+ "7": 7,
17
+ "7-5": 7.5,
18
+ "8": 8,
19
+ "8-5": 8.5,
20
+ "9": 9,
21
+ "9-5": 9.5,
22
+ "10": 10,
23
+ "10-5": 10.5,
24
+ "11": 11,
25
+ "11-5": 11.5,
26
+ "12": 12,
27
+ "12-5": 12.5,
28
+ "13": 13,
29
+ "13-5": 13.5,
30
+ "14": 14,
31
+ "14-5": 14.5,
32
+ "15": 15,
33
+ );
34
+
35
+ @each $name, $value in $iterations {
36
+ .width-#{$name} {
37
+ width: #{$value}rem;
38
+ }
39
+ }
40
+
41
+ .width-stretch {
42
+ width: stretch;
43
+ }
package/scss/index.scss CHANGED
@@ -8,6 +8,7 @@
8
8
  @use "br-form-checkbox";
9
9
  @use "br-form-edit";
10
10
  @use "br-form-fieldset";
11
+ @use "br-form-label";
11
12
  @use "br-form-radio";
12
13
  @use "br-form-row";
13
14
  @use "br-form-textarea";
@@ -21,7 +22,9 @@
21
22
  @use "br-promo";
22
23
  @use "br-select";
23
24
  @use "br-skiplink";
25
+ @use "br-table-cell";
24
26
  @use "br-table-header";
27
+ @use "br-table-row";
25
28
  @use "br-wrap";
26
29
 
27
30
  @use "city-of-london";
@@ -1,4 +1,5 @@
1
1
  @use "../mixins/breakpoints" as *;
2
+ @use "../mixins/font-size" as *;
2
3
  @use "card/default" as *;
3
4
  @use "card/inline" as *;
4
5
  @use "card/membership" as *;
@@ -62,11 +63,7 @@
62
63
  }
63
64
 
64
65
  @mixin br-card-subtitle {
65
- font-size: var(--font-size-h5);
66
-
67
- @include medium-up {
68
- font-size: var(--font-size-h4);
69
- }
66
+ @include font-size-subtitle;
70
67
  }
71
68
 
72
69
  @mixin br-card--body {
@@ -114,6 +111,6 @@
114
111
 
115
112
  @mixin br-card-text {
116
113
  +.br-card-text {
117
- margin-top: var(--margin-md);
114
+ margin-top: var(--margin-sm);
118
115
  }
119
116
  }
@@ -15,14 +15,14 @@
15
15
  @mixin br-form-radio--input {
16
16
  cursor: pointer;
17
17
 
18
- &:focus {
18
+ &:focus-visible {
19
19
  outline-color: var(--color-black-25-lighten);
20
20
  outline-offset: var(--outline-offset-lg);
21
21
  outline-width: var(--border-width-sm);
22
22
  outline-style: dashed;
23
23
  }
24
24
 
25
- &:checked:focus {
25
+ &:checked:focus-visible {
26
26
  @include br-form-radio--input-colors;
27
27
  }
28
28
  }
@@ -84,7 +84,7 @@
84
84
  cursor: pointer;
85
85
 
86
86
  &:hover,
87
- &:has(input:focus) {
87
+ &:has(input:focus-visible) {
88
88
  @include br-form-radio-label--disabled;
89
89
  @include br-form-radio-label--hover;
90
90
  }
@@ -1,5 +1,7 @@
1
1
  @use "../mixins/breakpoints" as *;
2
2
 
3
+ $sizes: xs, sm, md, lg;
4
+
3
5
  @mixin br-wrap--default {
4
6
  max-width: var(--width-layout-sm);
5
7
  margin-right: auto;
@@ -17,10 +19,14 @@
17
19
  }
18
20
 
19
21
  @mixin br-wrap--cluster {
20
- margin: var(--margin-wrap-xs);
22
+ @each $size in $sizes {
23
+ &.size-#{$size} {
24
+ margin: var(--margin-wrap-#{$size});
21
25
 
22
- >* {
23
- margin: var(--margin-xs);
26
+ >* {
27
+ margin: var(--margin-#{$size});
28
+ }
29
+ }
24
30
  }
25
31
 
26
32
  &.align-middle>* {
@@ -34,8 +40,7 @@
34
40
 
35
41
  @mixin br-wrap--preferences {
36
42
  @include small-up {
37
- grid-template-columns: var(--width-column) var(--width-column);
38
- display: grid;
43
+ display: flex;
39
44
  }
40
45
 
41
46
  @include small-down {
@@ -46,21 +51,19 @@
46
51
  @mixin br-wrap--orders {
47
52
  @include br-wrap--cluster;
48
53
 
49
- $margin: calc(var(--margin-xs) * 2);
54
+ @each $size in $sizes {
55
+ $margin: calc(var(--margin-#{$size}) * 2);
50
56
 
51
- @include medium-down {
52
- >* {
53
- width: calc(100% - $margin);
54
- }
55
- }
56
-
57
- @include medium-up {
58
- >* {
59
- width: calc(50% - $margin);
60
- }
57
+ &.size-#{$size} {
58
+ >* {
59
+ @include medium-down {
60
+ width: calc(100% - $margin);
61
+ }
61
62
 
62
- >*.open {
63
- width: calc(100% - $margin);
63
+ @include medium-up {
64
+ width: calc(50% - $margin);
65
+ }
66
+ }
64
67
  }
65
68
  }
66
69
  }
@@ -0,0 +1,9 @@
1
+ @use "../mixins/breakpoints" as *;
2
+
3
+ @mixin font-size-subtitle {
4
+ font-size: var(--font-size-h5);
5
+
6
+ @include medium-up {
7
+ font-size: var(--font-size-h4);
8
+ }
9
+ }
@@ -1,4 +1,5 @@
1
1
  @use "../../../mixins/buttons/outline" as *;
2
+ @use "../../../mixins/buttons/slim" as *;
2
3
 
3
4
  $iterations: (
4
5
  "member": var(--color-brand-membership),
@@ -12,6 +13,7 @@ $iterations: (
12
13
 
13
14
  @mixin btn-renew-membership {
14
15
  @include outline-button($color: white, $background: transparent);
16
+ @include btn-slim;
15
17
 
16
18
  &:focus,
17
19
  &:hover {
@@ -18,6 +18,7 @@
18
18
  @forward "core";
19
19
  @forward "festival";
20
20
  @forward "focus";
21
+ @forward "font-size";
21
22
  @forward "headings";
22
23
  @forward "input";
23
24
  @forward "loading";
@@ -1,3 +1,5 @@
1
+ @use "../../mixins/breakpoints" as *;
2
+
1
3
  @mixin br-form-checkbox--input-colors {
2
4
  &:not(.error):not(.success) {
3
5
  outline-color: var(--color-status-neutral);
@@ -13,14 +15,14 @@
13
15
  }
14
16
 
15
17
  @mixin br-form-checkbox--input {
16
- &:focus {
18
+ &:focus-visible {
17
19
  outline-color: var(--color-black-50-lighten);
18
20
  outline-offset: var(--outline-offset-lg);
19
21
  outline-width: var(--border-width-sm);
20
22
  outline-style: dashed;
21
23
  }
22
24
 
23
- &:checked:focus {
25
+ &:checked:focus-visible {
24
26
  @include br-form-checkbox--input-colors;
25
27
  }
26
28
  }
@@ -79,11 +81,26 @@
79
81
  }
80
82
  }
81
83
 
84
+ &.returns {
85
+ border-bottom-right-radius: 0;
86
+ border-bottom-left-radius: 0;
87
+ border-width: 0;
88
+
89
+ @include medium-up {
90
+ border-top-right-radius: 0;
91
+ border-top-left-radius: 0;
92
+ }
93
+
94
+ @include medium-down {
95
+ padding: var(--padding-lg);
96
+ }
97
+ }
98
+
82
99
  &:not(.disabled) {
83
100
  cursor: pointer;
84
101
 
85
102
  &:hover,
86
- &:has(input:focus) {
103
+ &:has(input:focus-visible) {
87
104
  @include br-form-checkbox-label--disabled;
88
105
  @include br-form-checkbox-label--hover;
89
106
  }
@@ -1,61 +0,0 @@
1
- import gsap from 'gsap'
2
-
3
- /** @type {gsap.TweenVars} Fade-in vars: animates element from transparent with a short delay */
4
- const fadeIn = {
5
- duration: 0.3,
6
- opacity: 0,
7
- delay: 0.3,
8
- }
9
-
10
- /** @type {gsap.TweenVars} Explode vars: scales element up and fades it out */
11
- const explode = {
12
- ease: 'power1.out',
13
- opacity: 0,
14
- scale: 1.5,
15
- }
16
-
17
- /** @type {gsap.TimelineVars} Default timeline settings shared across all confirm animations */
18
- const defaults = {
19
- duration: 0.6,
20
- ease: 'power1.in',
21
- transformOrigin: 'center',
22
- }
23
-
24
- /**
25
- * Animates the email confirmation card.
26
- * Fades in the title, draws the envelope outline and fold SVG paths,
27
- * explodes a cloned title, then slides in the arrow.
28
- */
29
- export const animateEmail = () => {
30
- const query = (target) => document.querySelector(`.card[email] ${target}`)
31
- const title = query('.card-title')
32
- const outline = query('.outline')
33
- const arrow = query('.arrow')
34
- const fold = query('.fold')
35
- const tl = gsap.timeline({ defaults })
36
-
37
- const clone = title.cloneNode(true)
38
- clone.classList.add('clone')
39
- title.after(clone)
40
-
41
- tl.set(arrow, { opacity: 0 }).from(title, fadeIn).set(clone, { opacity: 0.4 }).from(outline, { drawSVG: '0%' }, 'start').from(fold, { drawSVG: '0%', duration: 0.3 }, 'start').to(clone, explode, 'start').set(arrow, { opacity: 1 }).from(arrow, { x: -6, ease: 'power1.out' })
42
- }
43
-
44
- /**
45
- * Animates the done confirmation card.
46
- * Fades in the title, draws the circle outline and tick SVG paths,
47
- * and explodes a cloned title.
48
- */
49
- export const animateDone = () => {
50
- const query = (target) => document.querySelector(`.card[done] ${target}`)
51
- const title = query('.card-title')
52
- const outline = query('.outline')
53
- const tick = query('.tick')
54
- const tl = gsap.timeline({ defaults })
55
-
56
- const clone = title.cloneNode(true)
57
- clone.classList.add('clone')
58
- title.after(clone)
59
-
60
- tl.from(title, fadeIn).set(clone, { opacity: 0.4 }).from(outline, { drawSVG: '0%' }, 'start').from(tick, { drawSVG: '0%', duration: 0.3 }, 'start+=0.3').to(clone, explode, 'start')
61
- }
@@ -1,8 +0,0 @@
1
- import { animateEmail, animateDone } from '#animations/confirm'
2
-
3
- export default {
4
- methods: {
5
- animateEmail,
6
- animateDone,
7
- },
8
- }