92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import type { Entries, Entry, KeysOfUnion } from 'type-fest'
|
|
import type { HTMLAttributes } from 'vue'
|
|
|
|
export type AlignmentProps = {
|
|
alignText?: 'start' | 'center' | 'end' | 'stretch' | 'space-out',
|
|
alignSelf?: 'start' | 'center' | 'end' | 'auto' | 'baseline' | 'stretch',
|
|
alignItems?: 'start' | 'center' | 'end' | 'auto' | 'baseline' | 'stretch'
|
|
} & {
|
|
[T in 'center' | 'stretch']?: true
|
|
}
|
|
export type Key = KeysOfUnion<AlignmentProps>
|
|
|
|
const styles = {
|
|
center: 'place-content: center center; place-self: center center;',
|
|
stretch: 'place-content: stretch stretch; place-self: stretch stretch;',
|
|
alignText: (a:AlignmentProps['alignText']) => ({
|
|
start: 'justify-content: flex-start;',
|
|
center: 'place-content: center;',
|
|
baseline: 'align-items: baseline;',
|
|
end: 'justify-content: flex-end;',
|
|
stretch: 'place-content: stretch;',
|
|
'space-out': 'place-content: space-between;'
|
|
}[a!]),
|
|
alignSelf: (a:AlignmentProps['alignSelf']) => ({
|
|
start: 'align-self: flex-start;',
|
|
center: 'align-self: center;',
|
|
end: 'align-self: flex-end;',
|
|
auto: 'align-self: auto;',
|
|
baseline: 'align-self: baseline;',
|
|
stretch: 'align-self: stretch;'
|
|
}[a!]),
|
|
alignItems: (a:AlignmentProps['alignSelf']) => ({
|
|
start: 'align-items: flex-start;',
|
|
center: 'align-items: center;',
|
|
end: 'align-items: flex-end;',
|
|
auto: 'align-items: auto;',
|
|
baseline: 'align-items: baseline;',
|
|
stretch: 'align-items: stretch;'
|
|
}[a!])
|
|
} as const
|
|
|
|
const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>):string =>
|
|
(
|
|
typeof styles[key] === 'string'
|
|
? styles[key]
|
|
// @ts-expect-error We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
|
|
: (styles[key]((key in props && props[key]) ? props[((props[key]), (key))] : value))
|
|
)
|
|
|
|
const merge = (rules: string[]) => (attributes: HTMLAttributes = {}) =>
|
|
rules.length === 0
|
|
? attributes
|
|
: ({
|
|
...attributes,
|
|
style: rules.join(' ') + ('style' in attributes ? attributes.style + ' ' : '')
|
|
})
|
|
|
|
// All keys are exclusive
|
|
const conflicts: Set<Key>[] = [
|
|
new Set(['center', 'stretch']),
|
|
new Set(['alignText']),
|
|
new Set(['alignSelf'])
|
|
]
|
|
|
|
/**
|
|
* Add alignment styles to your component.
|
|
* Alignments are designed to work both in a grid and flex contexts but may fail in normal context.
|
|
*
|
|
* (1) Add `& AlignmentProps` to your `Props` type
|
|
* (2) Call `v-bind="alignment(props)"` on your component template
|
|
* (3) Now your component accepts width props such as `align-text="center"`.
|
|
*
|
|
* @param props Your component's props (or ...rest props if you have destructured them already)
|
|
* @param defaults These props are applied immediately and can be overridden by the user
|
|
* @param attributes Optional: To compose width, color, alignment, etc.
|
|
* @returns the corresponding `{ style }` object
|
|
*/
|
|
export const align = <TProps extends Partial<AlignmentProps>>(
|
|
props: TProps,
|
|
defaults: Partial<AlignmentProps> = {}
|
|
) => merge(
|
|
((Object.entries(props) as Entries<TProps>).reduce(
|
|
(acc, [key, value]) =>
|
|
value && key in styles
|
|
? acc.filter(([accKey, _]) => !conflicts.find(set => set.has(accKey) && set.has(key)))
|
|
.concat([[key, value]])
|
|
: acc
|
|
,
|
|
(Object.entries(defaults)) as Entries<Partial<AlignmentProps>>
|
|
)).map(getStyle(props))
|
|
)
|